Merge branch 'next' into add-hostname
This commit is contained in:
290
CHANGELOG.md
290
CHANGELOG.md
@@ -4,20 +4,166 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(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
|
||||||
|
|
||||||
|
## [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
|
||||||
|
- *(readme)* Add new sponsors Supadata AI and WZ-IT to the README
|
||||||
|
- *(core)* Enable magic env variables for compose based applications
|
||||||
|
|
||||||
|
### 🐛 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
|
||||||
|
- *(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
|
||||||
|
|
||||||
|
### 💼 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
|
||||||
|
- *(source)* Conditionally display connected source and change source options based on private key presence
|
||||||
|
|
||||||
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
- Update changelog
|
||||||
|
- Update changelog
|
||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
- *(versions)* Bump version to 404
|
- *(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
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
### 🐛 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
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(versions)* Bump version to 406
|
||||||
|
- *(versions)* Bump version to 407
|
||||||
|
|
||||||
## [4.0.0-beta.404] - 2025-04-03
|
## [4.0.0-beta.404] - 2025-04-03
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(proxy)* Enhance proxy handling and port conflict detection
|
||||||
- *(lang)* Added Azerbaijani language updated turkish language. (#5497)
|
- *(lang)* Added Azerbaijani language updated turkish language. (#5497)
|
||||||
- *(lang)* Added Portuguese from Brazil language (#5500)
|
- *(lang)* Added Portuguese from Brazil language (#5500)
|
||||||
- *(lang)* Add Indonesian language translations (#5513)
|
- *(lang)* Add Indonesian language translations (#5513)
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(database)* Custom config for MongoDB (#5471)
|
||||||
|
- *(ui)* Instance Backup settings
|
||||||
- *(docs)* Comment out execute for now
|
- *(docs)* Comment out execute for now
|
||||||
- *(installation)* Mount the docker config
|
- *(installation)* Mount the docker config
|
||||||
- *(installation)* Path to config file for docker login
|
- *(installation)* Path to config file for docker login
|
||||||
@@ -27,10 +173,13 @@ All notable changes to this project will be documented in this file.
|
|||||||
- *(docs)* Contribute service url (#5517)
|
- *(docs)* Contribute service url (#5517)
|
||||||
- *(proxy)* Proxy restart does not work on domain
|
- *(proxy)* Proxy restart does not work on domain
|
||||||
- *(ui)* Only show copy button on https
|
- *(ui)* Only show copy button on https
|
||||||
- *(database)* Custom config for MongoDB (#5471)
|
|
||||||
|
|
||||||
### 📚 Documentation
|
### 📚 Documentation
|
||||||
|
|
||||||
|
- Update changelog
|
||||||
|
- Update changelog
|
||||||
|
- Update changelog
|
||||||
|
- Update changelog
|
||||||
- Update changelog
|
- Update changelog
|
||||||
- Update changelog
|
- Update changelog
|
||||||
- Update changelog
|
- Update changelog
|
||||||
@@ -38,9 +187,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(versions)* Bump version to 403 (#5520)
|
||||||
|
- *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404
|
||||||
- *(service)* Remove unused code in Bugsink service
|
- *(service)* Remove unused code in Bugsink service
|
||||||
- *(versions)* Update version to 404
|
- *(versions)* Update version to 404
|
||||||
- *(versions)* Bump version to 403 (#5520)
|
- *(versions)* Bump version to 404
|
||||||
|
|
||||||
## [4.0.0-beta.402] - 2025-04-01
|
## [4.0.0-beta.402] - 2025-04-01
|
||||||
|
|
||||||
@@ -59,7 +210,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- *(DeployController)* Cast 'pr' query parameter to integer
|
- *(DeployController)* Cast 'pr' query parameter to integer
|
||||||
- *(deploy)* Validate team ID before deployment
|
- *(deploy)* Validate team ID before deployment
|
||||||
- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424)
|
- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424)
|
||||||
- *(ui)* Instance Backup settings
|
|
||||||
|
|
||||||
### 🚜 Refactor
|
### 🚜 Refactor
|
||||||
|
|
||||||
@@ -73,7 +223,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- *(service)* Add google variables to plausible.yaml (#5429)
|
- *(service)* Add google variables to plausible.yaml (#5429)
|
||||||
- *(service)* Update authentik.yaml versions (#5373)
|
- *(service)* Update authentik.yaml versions (#5373)
|
||||||
- *(core)* Remove redocs
|
- *(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
|
## [4.0.0-beta.401] - 2025-03-28
|
||||||
|
|
||||||
@@ -144,6 +293,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(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
|
||||||
- *(service)* Neon
|
- *(service)* Neon
|
||||||
- *(migration)* Add `ssl_certificates` table and model
|
- *(migration)* Add `ssl_certificates` table and model
|
||||||
- *(migration)* Add ssl setting to `standalone_postgresqls` table
|
- *(migration)* Add ssl setting to `standalone_postgresqls` table
|
||||||
@@ -185,14 +342,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- *(ssl)* Improve Redis and remove modes
|
- *(ssl)* Improve Redis and remove modes
|
||||||
- Full SSL support for DrangonflyDB
|
- Full SSL support for DrangonflyDB
|
||||||
- SSL notification
|
- 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)* Change glance for v0.7
|
||||||
- *(templates)* Add Freescout service template
|
- *(templates)* Add Freescout service template
|
||||||
- *(service)* Add Evolution API template
|
- *(service)* Add Evolution API template
|
||||||
@@ -210,6 +359,18 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
- *(api)* Docker compose based apps creationg through api
|
- *(api)* Docker compose based apps creationg through api
|
||||||
- *(database)* Improve database type detection for Supabase Postgres images
|
- *(database)* Improve database type detection for Supabase Postgres images
|
||||||
|
- *(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
|
||||||
- *(ssl)* Permission of ssl crt and key inside the container
|
- *(ssl)* Permission of ssl crt and key inside the container
|
||||||
- *(ui)* Make sure file mounts do not showing the encrypted values
|
- *(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
|
- *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert
|
||||||
@@ -249,18 +410,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- *(ssl)* Add `--tls` arg to DrangflyDB
|
- *(ssl)* Add `--tls` arg to DrangflyDB
|
||||||
- *(notification)* Always send SSL notifications
|
- *(notification)* Always send SSL notifications
|
||||||
- *(database)* Change default value of enable_ssl to false for multiple tables
|
- *(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
|
- *(general)* Correct link to framework specific documentation
|
||||||
- *(core)* Redirect healthcheck route for dockercompose applications
|
- *(core)* Redirect healthcheck route for dockercompose applications
|
||||||
- *(api)* Use name from request payload
|
- *(api)* Use name from request payload
|
||||||
@@ -309,6 +458,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(supabase)* Update Supabase service template and Postgres image version
|
||||||
- *(migration)* Remove unused columns
|
- *(migration)* Remove unused columns
|
||||||
- *(ssl)* Improve code in ssl helper
|
- *(ssl)* Improve code in ssl helper
|
||||||
- *(migration)* Ssl cert and key should not be nullable
|
- *(migration)* Ssl cert and key should not be nullable
|
||||||
@@ -316,7 +466,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Rename ca crt folder to ssl
|
- Rename ca crt folder to ssl
|
||||||
- *(ui)* Improve valid until handling
|
- *(ui)* Improve valid until handling
|
||||||
- Improve code quality suggested by code rabbit
|
- 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
|
||||||
|
|
||||||
## [4.0.0-beta.398] - 2025-03-01
|
## [4.0.0-beta.398] - 2025-03-01
|
||||||
@@ -709,6 +858,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### 🚀 Features
|
### 🚀 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
|
||||||
- Able to import full db backups for pg/mysql/mariadb
|
- Able to import full db backups for pg/mysql/mariadb
|
||||||
- Restore backup from server file
|
- Restore backup from server file
|
||||||
- Docker volume data cloning
|
- Docker volume data cloning
|
||||||
@@ -744,6 +901,35 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- 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
|
||||||
- Compose envs
|
- Compose envs
|
||||||
- Scheduled tasks and backups are executed by server timezone.
|
- Scheduled tasks and backups are executed by server timezone.
|
||||||
- Show backup timezone on the UI
|
- Show backup timezone on the UI
|
||||||
@@ -843,6 +1029,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### 🚜 Refactor
|
### 🚜 Refactor
|
||||||
|
|
||||||
|
- Rename `coolify.environment` to `coolify.environmentName`
|
||||||
- Rename parameter in DatabaseBackupJob for clarity
|
- Rename parameter in DatabaseBackupJob for clarity
|
||||||
- Improve checkbox component accessibility and styling
|
- Improve checkbox component accessibility and styling
|
||||||
- Remove unused tags method from ApplicationDeploymentJob
|
- Remove unused tags method from ApplicationDeploymentJob
|
||||||
@@ -858,6 +1045,9 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Regenerate API spec, removing notification fields
|
||||||
|
- Remove ray debugging
|
||||||
|
- Version ++
|
||||||
- Improve Penpot healthchecks
|
- Improve Penpot healthchecks
|
||||||
- Switch up readonly lables to make more sense
|
- Switch up readonly lables to make more sense
|
||||||
- Remove unused computed fields
|
- Remove unused computed fields
|
||||||
@@ -881,44 +1071,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### 🚀 Features
|
### 🚀 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
|
- Migrate to new encryption options
|
||||||
- New encryption options
|
- New encryption options
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 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
|
- Smtp encryption
|
||||||
- Bind() to 0.0.0.0:80 failed
|
- Bind() to 0.0.0.0:80 failed
|
||||||
- Oauth seeder
|
- Oauth seeder
|
||||||
@@ -928,15 +1085,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Error message
|
- Error message
|
||||||
- Update healthcheck and port configurations to use port 8080
|
- Update healthcheck and port configurations to use port 8080
|
||||||
|
|
||||||
### 🚜 Refactor
|
## [4.0.0-beta.379] - 2024-12-13
|
||||||
|
|
||||||
- Rename `coolify.environment` to `coolify.environmentName`
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
- Saving oauth
|
||||||
|
|
||||||
- Regenerate API spec, removing notification fields
|
|
||||||
- Remove ray debugging
|
|
||||||
- Version ++
|
|
||||||
|
|
||||||
## [4.0.0-beta.378] - 2024-12-13
|
## [4.0.0-beta.378] - 2024-12-13
|
||||||
|
|
||||||
@@ -945,11 +1098,6 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Monaco editor light and dark mode switching
|
- Monaco editor light and dark mode switching
|
||||||
- Service status indicator + oauth saving
|
- Service status indicator + oauth saving
|
||||||
- Socialite for azure and authentik
|
- 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
|
## [4.0.0-beta.377] - 2024-12-13
|
||||||
|
|
||||||
|
183
README.md
183
README.md
@@ -29,99 +29,6 @@ You can find the installation script source [here](./scripts/install.sh).
|
|||||||
|
|
||||||
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||||
|
|
||||||
# Donations
|
|
||||||
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
|
|
||||||
|
|
||||||
[coolify.io/sponsorships](https://coolify.io/sponsorships)
|
|
||||||
|
|
||||||
Thank you so much!
|
|
||||||
|
|
||||||
Special thanks to our biggest sponsors!
|
|
||||||
|
|
||||||
### Special Sponsors
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry.
|
|
||||||
* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions.
|
|
||||||
* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities.
|
|
||||||
* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform.
|
|
||||||
* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies.
|
|
||||||
* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution.
|
|
||||||
* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks.
|
|
||||||
* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase.
|
|
||||||
* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
|
||||||
* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management.
|
|
||||||
* [Convex](https://convex.link/coolify.io) - Convex is the open-source reactive database for web app developers.
|
|
||||||
* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
|
||||||
* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers.
|
|
||||||
* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang.
|
|
||||||
* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets.
|
|
||||||
* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers.
|
|
||||||
* [Brand Dev](https://brand.dev/?ref=coolify.io) - The #1 Brand API for B2B software startups - instantly pull logos, fonts, descriptions, social links, slogans, and so much more from any domain via a single api call.
|
|
||||||
* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries.
|
|
||||||
* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools.
|
|
||||||
* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services.
|
|
||||||
* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services.
|
|
||||||
* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses.
|
|
||||||
* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly.
|
|
||||||
* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes.
|
|
||||||
* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - A Fast web hosting provider.
|
|
||||||
|
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
|
||||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
|
||||||
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
|
||||||
<a href="https://www.runpod.io/?ref=coolify.io">
|
|
||||||
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
|
||||||
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
|
||||||
<a href="https://dartnode.com/?ref=coolify.io"><img src="https://github.com/DartNode-com.png" width="60px" alt="DartNode"/></a>
|
|
||||||
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
|
||||||
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
|
||||||
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
|
||||||
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
|
|
||||||
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
|
||||||
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
|
||||||
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
|
||||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
|
||||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
|
||||||
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
|
|
||||||
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
|
|
||||||
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
|
|
||||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
|
||||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
|
||||||
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
|
||||||
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
|
|
||||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
|
||||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
|
||||||
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
|
||||||
<a href="https://startupfa.me?utm_source=coolify.io"><img src="https://github.com/startupfame.png" width="60px" alt="StartupFame" /></a>
|
|
||||||
<a href="https://bsky.app/profile/jyc.dev"><img src="https://github.com/jycouet.png" width="60px" alt="jyc.dev" /></a>
|
|
||||||
<a href="https://bitlaunch.io/?utm_source=coolify.io"><img src="https://github.com/bitlaunchio.png" width="60px" alt="BitLaunch" /></a>
|
|
||||||
<a href="https://internetgarden.co/?utm_source=coolify.io"><img src="./other/logos/internetgarden.ico" width="60px" alt="Internet Garden" /></a>
|
|
||||||
<a href="https://jonasjaeger.com?utm_source=coolify.io"><img src="https://github.com/toxin20.png" width="60px" alt="Jonas Jaeger" /></a>
|
|
||||||
<a href="https://github.com/therealjp?utm_source=coolify.io"><img src="https://github.com/therealjp.png" width="60px" alt="JP" /></a>
|
|
||||||
<a href="https://evercam.io/?utm_source=coolify.io"><img src="https://github.com/evercam.png" width="60px" alt="Evercam" /></a>
|
|
||||||
<a href="https://web3.career/?utm_source=coolify.io"><img src="https://web3.career/favicon1.png" width="60px" alt="Web3 Career" /></a>
|
|
||||||
|
|
||||||
## Organizations
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
|
||||||
|
|
||||||
|
|
||||||
## Individuals
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
|
||||||
|
|
||||||
# Cloud
|
# Cloud
|
||||||
|
|
||||||
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
|
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
|
||||||
@@ -137,6 +44,96 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
- Better support
|
- Better support
|
||||||
- Less maintenance for you
|
- Less maintenance for you
|
||||||
|
|
||||||
|
# Donations
|
||||||
|
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
|
||||||
|
|
||||||
|
[coolify.io/sponsorships](https://coolify.io/sponsorships)
|
||||||
|
|
||||||
|
Thank you so much!
|
||||||
|
|
||||||
|
## Big Sponsors
|
||||||
|
|
||||||
|
* [GlueOps](https://www.glueops.dev?ref=coolify.io) - DevOps automation and infrastructure management
|
||||||
|
* [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
|
||||||
|
* [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
|
||||||
|
* [Darweb](https://darweb.nl/?ref=coolify.io) - Design. Develop. Deliver. Specialized in 3D CPQ Solutions
|
||||||
|
* [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 transformation and web solutions
|
||||||
|
* [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
|
||||||
|
* [Syntax.fm](https://syntax.fm?ref=coolify.io) - Podcast for web developers
|
||||||
|
* [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
|
||||||
|
|
||||||
|
## Small Sponsors
|
||||||
|
|
||||||
|
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||||
|
<a href="https://evercam.io/?utm_source=coolify.io"><img width="60px" alt="Evercam" src="https://github.com/evercam.png"/></a>
|
||||||
|
<a href="https://github.com/iujlaki"><img width="60px" alt="Imre Ujlaki" src="https://github.com/iujlaki.png"/></a>
|
||||||
|
<a href="https://bsky.app/profile/jyc.dev"><img width="60px" alt="jyc.dev" src="https://github.com/jycouet.png"/></a>
|
||||||
|
<a href="https://github.com/therealjp?utm_source=coolify.io"><img width="60px" alt="TheRealJP" src="https://github.com/therealjp.png"/></a>
|
||||||
|
<a href="https://360creators.com/?utm_source=coolify.io"><img width="60px" alt="360Creators" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/503e0953-bff7-4296-b4cc-5e36d40eecc0/icon-360creators.png"/></a>
|
||||||
|
<a href="https://github.com/aniftyco"><img width="60px" alt="NiftyCo" src="https://github.com/aniftyco.png"/></a>
|
||||||
|
<a href="https://dry.software/?utm_source=coolify.io"><img width="60px" alt="Dry Software" src="https://github.com/dry-software.png"/></a>
|
||||||
|
<a href="https://lightspeed.run/?utm_source=coolify.io"><img width="60px" alt="Lightspeed.run" src="https://github.com/lightspeedrun.png"/></a>
|
||||||
|
<a href="https://linkdr.com?utm_source=coolify.io"><img width="60px" alt="LinkDr" src="https://github.com/LLM-Inc.png"/></a>
|
||||||
|
<a href="http://gravitywiz.com/?utm_source=coolify.io"><img width="60px" alt="Gravity Wiz" src="https://github.com/gravitywiz.png"/></a>
|
||||||
|
<a href="https://bitlaunch.io/?utm_source=coolify.io"><img width="60px" alt="BitLaunch" src="https://github.com/bitlaunchio.png"/></a>
|
||||||
|
<a href="https://bestforandroid.com/?utm_source=coolify.io"><img width="60px" alt="Best for Android" src="https://github.com/bestforandroid.png"/></a>
|
||||||
|
<a href="https://il.ly/?utm_source=coolify.io"><img width="60px" alt="Ilias Ism" src="https://github.com/Illyism.png"/></a>
|
||||||
|
<a href="https://formbricks.com/?utm_source=coolify.io"><img width="60px" alt="Formbricks" src="https://github.com/formbricks.png"/></a>
|
||||||
|
<a href="https://www.serversearcher.com/"><img width="60px" alt="Server Searcher" src="https://github.com/serversearcher.png"/></a>
|
||||||
|
<a href="https://www.reshot.ai/?utm_source=coolify.io"><img width="60px" alt="Reshot" src="https://coolify.io/images/reshotai.png"/></a>
|
||||||
|
<a href="https://cirun.io/?utm_source=coolify.io"><img width="60px" alt="Cirun" src="https://coolify.io/images/cirun-logo.png"/></a>
|
||||||
|
<a href="https://typebot.io/?utm_source=coolify.io"><img width="60px" alt="Typebot" src="https://cdn.bsky.app/img/avatar/plain/did:plc:gwxcta3pccyim4z5vuultdqx/bafkreig23hci7e2qpdxicsshnuzujbcbcgmydxhbybkewszdezhdodv42m@jpeg"/></a>
|
||||||
|
<a href="https://cccareers.org/?utm_source=coolify.io"><img width="60px" alt="Creating Coding Careers" src="https://github.com/cccareers.png"/></a>
|
||||||
|
<a href="https://internetgarden.co/?utm_source=coolify.io"><img width="60px" alt="Internet Garden" src="https://coolify.io/images/internetgarden.ico"/></a>
|
||||||
|
<a href="https://web3.career/?utm_source=coolify.io"><img width="60px" alt="Web3 Jobs" src="https://coolify.io/images/web3jobs.png"/></a>
|
||||||
|
<a href="https://codext.link/coolify-io?utm_source=coolify.io"><img width="60px" alt="Codext" src="https://coolify.io/images/codext.jpg"/></a>
|
||||||
|
<a href="https://github.com/monocursive"><img width="60px" alt="Michael Mazurczak" src="https://github.com/monocursive.png"/></a>
|
||||||
|
<a href="https://fider.io/?utm_source=coolify.io"><img width="60px" alt="Fider" src="https://github.com/getfider.png"/></a>
|
||||||
|
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"><img width="60px" alt="Flint" src="https://github.com/Flint-company.png"/></a>
|
||||||
|
<a href="https://github.com/urtho"><img width="60px" alt="Paweł Pierścionek" src="https://github.com/urtho.png"/></a>
|
||||||
|
<a href="https://www.runpod.io/?utm_source=coolify.io"><img width="60px" alt="RunPod" src="https://coolify.io/images/runpod.svg"/></a>
|
||||||
|
<a href="https://dartnode.com/?utm_source=coolify.io"><img width="60px" alt="DartNode" src="https://github.com/dartnode.png"/></a>
|
||||||
|
<a href="https://github.com/whitesidest"><img width="60px" alt="Tyler Whitesides" src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4"/></a>
|
||||||
|
<a href="https://serpapi.com/?utm_source=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
|
<a href="https://aquarela.io"><img width="60px" alt="Aquarela" src="https://github.com/aquarela-io.png"/></a>
|
||||||
|
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img width="60px" alt="Crypto Jobs List" src="https://github.com/cryptojobslist.png"/></a>
|
||||||
|
<a href="https://www.youtube.com/@AlfredNutile?utm_source=coolify.io"><img width="60px" alt="Alfred Nutile" src="https://github.com/alnutile.png"/></a>
|
||||||
|
<a href="https://startupfa.me?utm_source=coolify.io"><img width="60px" alt="Startup Fame" src="https://github.com/startupfame.png"/></a>
|
||||||
|
<a href="https://barrad.me/?utm_source=coolify.io"><img width="60px" alt="Younes Barrad" src="https://github.com/Flowko.png"/></a>
|
||||||
|
<a href="https://jonasjaeger.com?utm_source=coolify.io"><img width="60px" alt="Jonas Jaeger" src="https://github.com/toxin20.png"/></a>
|
||||||
|
<a href="https://pixel.ao/?utm_source=coolify.io"><img width="60px" alt="Pixel Infinito" src="https://github.com/pixelinfinito.png"/></a>
|
||||||
|
<a href="https://github.com/corentinclichy"><img width="60px" alt="Corentin Clichy" src="https://github.com/corentinclichy.png"/></a>
|
||||||
|
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
||||||
|
<a href="https://devhuset.no?utm_source=coolify.io"><img width="60px" alt="Devhuset" src="https://github.com/devhuset.png"/></a>
|
||||||
|
<a href="https://arvensis.systems/?utm_source=coolify.io"><img width="60px" alt="Arvensis Systems" src="https://coolify.io/images/arvensis.png"/></a>
|
||||||
|
<a href="https://github.com/Niki2k1"><img width="60px" alt="Niklas Lausch" src="https://github.com/Niki2k1.png"/></a>
|
||||||
|
<a href="https://capgo.app/?utm_source=coolify.io"><img width="60px" alt="Cap-go" src="https://github.com/cap-go.png"/></a>
|
||||||
|
|
||||||
|
...and many more at [GitHub Sponsors](https://github.com/sponsors/coollabsio)
|
||||||
|
|
||||||
# Recognitions
|
# Recognitions
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@@ -217,6 +217,10 @@ class StartMongodb
|
|||||||
if ($this->database->enable_ssl) {
|
if ($this->database->enable_ssl) {
|
||||||
$commandParts = ['mongod'];
|
$commandParts = ['mongod'];
|
||||||
|
|
||||||
|
if (! empty($this->database->mongo_conf)) {
|
||||||
|
$commandParts = ['mongod', '--config', '/etc/mongo/mongod.conf'];
|
||||||
|
}
|
||||||
|
|
||||||
$sslConfig = match ($this->database->ssl_mode) {
|
$sslConfig = match ($this->database->ssl_mode) {
|
||||||
'allow' => [
|
'allow' => [
|
||||||
'--tlsMode=allowTLS',
|
'--tlsMode=allowTLS',
|
||||||
|
@@ -27,7 +27,7 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (! $server->isProxyShouldRun()) {
|
if (! $server->isProxyShouldRun()) {
|
||||||
@@ -37,8 +37,12 @@ class CheckProxy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine proxy container name based on environment
|
||||||
|
$proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||||
|
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
$server->proxy->set('status', $status);
|
$server->proxy->set('status', $status);
|
||||||
$server->save();
|
$server->save();
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
@@ -47,7 +51,7 @@ class CheckProxy
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$status = getContainerStatus($server, 'coolify-proxy');
|
$status = getContainerStatus($server, $proxyContainerName);
|
||||||
if ($status === 'running') {
|
if ($status === 'running') {
|
||||||
$server->proxy->set('status', 'running');
|
$server->proxy->set('status', 'running');
|
||||||
$server->save();
|
$server->save();
|
||||||
@@ -61,34 +65,11 @@ class CheckProxy
|
|||||||
if ($server->id === 0) {
|
if ($server->id === 0) {
|
||||||
$ip = 'host.docker.internal';
|
$ip = 'host.docker.internal';
|
||||||
}
|
}
|
||||||
|
|
||||||
$portsToCheck = ['80', '443'];
|
$portsToCheck = ['80', '443'];
|
||||||
|
|
||||||
foreach ($portsToCheck as $port) {
|
foreach ($portsToCheck as $port) {
|
||||||
// Try multiple methods to check port availability
|
// Use the smart port checker that handles dual-stack properly
|
||||||
$commands = [
|
if ($this->isPortConflict($server, $port, $proxyContainerName)) {
|
||||||
// Method 1: Check /proc/net/tcp directly (convert port to hex)
|
|
||||||
"cat /proc/net/tcp | grep -q '00000000:".str_pad(dechex($port), 4, '0', STR_PAD_LEFT)."'",
|
|
||||||
// Method 2: Use ss command (modern alternative to netstat)
|
|
||||||
"ss -tuln | grep -q ':$port '",
|
|
||||||
// Method 3: Use lsof if available
|
|
||||||
"lsof -i :$port >/dev/null 2>&1",
|
|
||||||
// Method 4: Use fuser if available
|
|
||||||
"fuser $port/tcp >/dev/null 2>&1",
|
|
||||||
];
|
|
||||||
|
|
||||||
$portInUse = false;
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
try {
|
|
||||||
instant_remote_process([$command], $server);
|
|
||||||
$portInUse = true;
|
|
||||||
break;
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($portInUse) {
|
|
||||||
if ($fromUI) {
|
if ($fromUI) {
|
||||||
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
|
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br><br>Docs: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' class='dark:text-white hover:underline' href='https://coolify.io/discord'>https://coolify.io/discord</a>");
|
||||||
} else {
|
} else {
|
||||||
@@ -126,4 +107,144 @@ class CheckProxy
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smart port checker that handles dual-stack configurations
|
||||||
|
* Returns true only if there's a real port conflict (not just dual-stack)
|
||||||
|
*/
|
||||||
|
private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool
|
||||||
|
{
|
||||||
|
// First check if our own proxy is using this port (which is fine)
|
||||||
|
try {
|
||||||
|
$getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'";
|
||||||
|
$containerId = trim(instant_remote_process([$getProxyContainerId], $server));
|
||||||
|
|
||||||
|
if (! empty($containerId)) {
|
||||||
|
$checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'";
|
||||||
|
try {
|
||||||
|
instant_remote_process([$checkProxyPort], $server);
|
||||||
|
|
||||||
|
// Our proxy is using the port, which is fine
|
||||||
|
return false;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Our container exists but not using this port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Container not found or error checking, continue with regular checks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command sets for different ways to check ports, ordered by preference
|
||||||
|
$commandSets = [
|
||||||
|
// Set 1: Use ss to check listener counts by protocol stack
|
||||||
|
[
|
||||||
|
'available' => 'command -v ss >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"",
|
||||||
|
// Count IPv4 listeners
|
||||||
|
"echo \"\$ss_output\" | grep -c ':$port '",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 2: Use netstat as alternative to ss
|
||||||
|
[
|
||||||
|
'available' => 'command -v netstat >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get listening process details
|
||||||
|
"netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '",
|
||||||
|
// Count listeners
|
||||||
|
"echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// Set 3: Use lsof as last resort
|
||||||
|
[
|
||||||
|
'available' => 'command -v lsof >/dev/null 2>&1',
|
||||||
|
'check' => [
|
||||||
|
// Get process using the port
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN'",
|
||||||
|
// Count listeners
|
||||||
|
"lsof -i :$port -P -n | grep 'LISTEN' | wc -l",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Try each command set until we find one available
|
||||||
|
foreach ($commandSets as $set) {
|
||||||
|
try {
|
||||||
|
// Check if the command is available
|
||||||
|
instant_remote_process([$set['available']], $server);
|
||||||
|
|
||||||
|
// Run the actual check commands
|
||||||
|
$output = instant_remote_process($set['check'], $server, true);
|
||||||
|
|
||||||
|
// Parse the output lines
|
||||||
|
$lines = explode("\n", trim($output));
|
||||||
|
|
||||||
|
// Get the detailed output and listener count
|
||||||
|
$details = trim($lines[0] ?? '');
|
||||||
|
$count = intval(trim($lines[1] ?? '0'));
|
||||||
|
|
||||||
|
// If no listeners or empty result, port is free
|
||||||
|
if ($count == 0 || empty($details)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect if this is our coolify-proxy
|
||||||
|
if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) {
|
||||||
|
// It's likely our docker or proxy, which is fine
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6)
|
||||||
|
// If exactly 2 listeners and both have same port, likely dual-stack
|
||||||
|
if ($count <= 2) {
|
||||||
|
// Check if it looks like a standard dual-stack setup
|
||||||
|
$isDualStack = false;
|
||||||
|
|
||||||
|
// Look for IPv4 and IPv6 in the listing (ss output format)
|
||||||
|
if (preg_match('/LISTEN.*:'.$port.'\s/', $details) &&
|
||||||
|
(preg_match('/\*:'.$port.'\s/', $details) ||
|
||||||
|
preg_match('/:::'.$port.'\s/', $details))) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For netstat format
|
||||||
|
if (strpos($details, '0.0.0.0:'.$port) !== false &&
|
||||||
|
strpos($details, ':::'.$port) !== false) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For lsof format (IPv4 and IPv6)
|
||||||
|
if (strpos($details, '*:'.$port) !== false &&
|
||||||
|
preg_match('/\*:'.$port.'.*IPv4/', $details) &&
|
||||||
|
preg_match('/\*:'.$port.'.*IPv6/', $details)) {
|
||||||
|
$isDualStack = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isDualStack) {
|
||||||
|
return false; // This is just a normal dual-stack setup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, it's likely a real port conflict
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// This command set failed, try the next one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to simpler check if all above methods fail
|
||||||
|
try {
|
||||||
|
// Just try to bind to the port directly to see if it's available
|
||||||
|
$checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'";
|
||||||
|
$result = instant_remote_process([$checkCommand], $server, true);
|
||||||
|
|
||||||
|
return trim($result) === 'in-use';
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// If everything fails, assume the port is free to avoid false positives
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,15 +14,26 @@ class CleanupDocker
|
|||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
$realtimeImage = config('constants.coolify.realtime_image');
|
||||||
|
$realtimeImageVersion = config('constants.coolify.realtime_version');
|
||||||
|
$realtimeImageWithVersion = "$realtimeImage:$realtimeImageVersion";
|
||||||
|
$realtimeImageWithoutPrefix = 'coollabsio/coolify-realtime';
|
||||||
|
$realtimeImageWithoutPrefixVersion = "coollabsio/coolify-realtime:$realtimeImageVersion";
|
||||||
|
|
||||||
$helperImageVersion = data_get($settings, 'helper_version');
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
$helperImage = config('constants.coolify.helper_image');
|
$helperImage = config('constants.coolify.helper_image');
|
||||||
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
|
$helperImageWithoutPrefix = 'coollabsio/coolify-helper';
|
||||||
|
$helperImageWithoutPrefixVersion = "coollabsio/coolify-helper:$helperImageVersion";
|
||||||
|
|
||||||
$commands = [
|
$commands = [
|
||||||
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
|
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
|
||||||
'docker image prune -af --filter "label!=coolify.managed=true"',
|
'docker image prune -af --filter "label!=coolify.managed=true"',
|
||||||
'docker builder prune -af',
|
'docker builder prune -af',
|
||||||
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
|
"docker images --filter before=$realtimeImageWithVersion --filter reference=$realtimeImage | grep $realtimeImage | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
|
"docker images --filter before=$helperImageWithoutPrefixVersion --filter reference=$helperImageWithoutPrefix | grep $helperImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
|
"docker images --filter before=$realtimeImageWithoutPrefixVersion --filter reference=$realtimeImageWithoutPrefix | grep $realtimeImageWithoutPrefix | awk '{print $3}' | xargs -r docker rmi -f",
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($server->settings->delete_unused_volumes) {
|
if ($server->settings->delete_unused_volumes) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Generate;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
@@ -8,7 +8,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
|
|
||||||
class OpenApi extends Command
|
class OpenApi extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'openapi';
|
protected $signature = 'generate:openapi';
|
||||||
|
|
||||||
protected $description = 'Generate OpenApi file.';
|
protected $description = 'Generate OpenApi file.';
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class OpenApi extends Command
|
|||||||
echo "Generating OpenAPI documentation.\n";
|
echo "Generating OpenAPI documentation.\n";
|
||||||
// https://github.com/OAI/OpenAPI-Specification/releases
|
// https://github.com/OAI/OpenAPI-Specification/releases
|
||||||
$process = Process::run([
|
$process = Process::run([
|
||||||
'/var/www/html/vendor/bin/openapi',
|
'./vendor/bin/openapi',
|
||||||
'app',
|
'app',
|
||||||
'-o',
|
'-o',
|
||||||
'openapi.yaml',
|
'openapi.yaml',
|
@@ -1,17 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands\Generate;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class ServicesGenerate extends Command
|
class Services extends Command
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
protected $signature = 'services:generate';
|
protected $signature = 'generate:services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
@@ -880,12 +880,17 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
LoadComposeFile::dispatch($application);
|
LoadComposeFile::dispatch($application);
|
||||||
@@ -1004,12 +1009,17 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
LoadComposeFile::dispatch($application);
|
LoadComposeFile::dispatch($application);
|
||||||
@@ -1101,12 +1111,17 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
LoadComposeFile::dispatch($application);
|
LoadComposeFile::dispatch($application);
|
||||||
@@ -1190,12 +1205,17 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
@@ -1254,12 +1274,17 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
@@ -1610,6 +1635,18 @@ class ApplicationsController extends Controller
|
|||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Applications'],
|
tags: ['Applications'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
requestBody: new OA\RequestBody(
|
requestBody: new OA\RequestBody(
|
||||||
description: 'Application updated.',
|
description: 'Application updated.',
|
||||||
required: true,
|
required: true,
|
||||||
@@ -1884,11 +1921,16 @@ class ApplicationsController extends Controller
|
|||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -2520,10 +2562,6 @@ class ApplicationsController extends Controller
|
|||||||
])->setStatusCode(201);
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Something went wrong.',
|
|
||||||
], 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Delete(
|
#[OA\Delete(
|
||||||
@@ -2705,13 +2743,21 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: $force,
|
force_rebuild: $force,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
no_questions_asked: $instant_deploy
|
no_questions_asked: $instant_deploy
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => $result['message'],
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -2866,12 +2912,17 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
restart_only: true,
|
restart_only: true,
|
||||||
is_api: true,
|
is_api: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
return response()->json([
|
||||||
|
'message' => $result['message'],
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -3006,73 +3057,73 @@ class ApplicationsController extends Controller
|
|||||||
// ]);
|
// ]);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// private function validateDataApplications(Request $request, Server $server)
|
private function validateDataApplications(Request $request, Server $server)
|
||||||
// {
|
{
|
||||||
// $teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
||||||
// // Validate ports_mappings
|
// Validate ports_mappings
|
||||||
// if ($request->has('ports_mappings')) {
|
if ($request->has('ports_mappings')) {
|
||||||
// $ports = [];
|
$ports = [];
|
||||||
// foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
||||||
// $port = explode(':', $portMapping);
|
$port = explode(':', $portMapping);
|
||||||
// if (in_array($port[0], $ports)) {
|
if (in_array($port[0], $ports)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'ports_mappings' => 'The first number before : should be unique between mappings.',
|
'ports_mappings' => 'The first number before : should be unique between mappings.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// $ports[] = $port[0];
|
$ports[] = $port[0];
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// // Validate custom_labels
|
// Validate custom_labels
|
||||||
// if ($request->has('custom_labels')) {
|
if ($request->has('custom_labels')) {
|
||||||
// if (! isBase64Encoded($request->custom_labels)) {
|
if (! isBase64Encoded($request->custom_labels)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'custom_labels' => 'The custom_labels should be base64 encoded.',
|
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// $customLabels = base64_decode($request->custom_labels);
|
$customLabels = base64_decode($request->custom_labels);
|
||||||
// if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'custom_labels' => 'The custom_labels should be base64 encoded.',
|
'custom_labels' => 'The custom_labels should be base64 encoded.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// if ($request->has('domains') && $server->isProxyShouldRun()) {
|
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||||
// $uuid = $request->uuid;
|
$uuid = $request->uuid;
|
||||||
// $fqdn = $request->domains;
|
$fqdn = $request->domains;
|
||||||
// $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||||
// $fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||||
// $errors = [];
|
$errors = [];
|
||||||
// $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
$fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
|
||||||
// if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
|
||||||
// $errors[] = 'Invalid domain: '.$domain;
|
$errors[] = 'Invalid domain: '.$domain;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return str($domain)->trim()->lower();
|
return str($domain)->trim()->lower();
|
||||||
// });
|
});
|
||||||
// if (count($errors) > 0) {
|
if (count($errors) > 0) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => $errors,
|
'errors' => $errors,
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
|
||||||
// return response()->json([
|
return response()->json([
|
||||||
// 'message' => 'Validation failed.',
|
'message' => 'Validation failed.',
|
||||||
// 'errors' => [
|
'errors' => [
|
||||||
// 'domains' => 'One of the domain is already used.',
|
'domains' => 'One of the domain is already used.',
|
||||||
// ],
|
],
|
||||||
// ], 422);
|
], 422);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
@@ -132,7 +133,7 @@ class DeployController extends Controller
|
|||||||
|
|
||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
summary: 'Deploy',
|
summary: 'Deploy',
|
||||||
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
description: 'Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.',
|
||||||
path: '/deploy',
|
path: '/deploy',
|
||||||
operationId: 'deploy-by-tag-or-uuid',
|
operationId: 'deploy-by-tag-or-uuid',
|
||||||
security: [
|
security: [
|
||||||
@@ -191,10 +192,10 @@ class DeployController extends Controller
|
|||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
$uuids = $request->query->get('uuid');
|
$uuids = $request->input('uuid');
|
||||||
$tags = $request->query->get('tag');
|
$tags = $request->input('tag');
|
||||||
$force = $request->query->get('force') ?? false;
|
$force = $request->input('force') ?? false;
|
||||||
$pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0;
|
$pr = $request->input('pr') ? max((int) $request->input('pr'), 0) : 0;
|
||||||
|
|
||||||
if ($uuids && $tags) {
|
if ($uuids && $tags) {
|
||||||
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
@@ -297,17 +298,21 @@ class DeployController extends Controller
|
|||||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
}
|
}
|
||||||
switch ($resource?->getMorphClass()) {
|
switch ($resource?->getMorphClass()) {
|
||||||
case \App\Models\Application::class:
|
case Application::class:
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $resource,
|
application: $resource,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: $force,
|
force_rebuild: $force,
|
||||||
pull_request_id: $pr,
|
pull_request_id: $pr,
|
||||||
);
|
);
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
if ($result['status'] === 'skipped') {
|
||||||
|
$message = $result['message'];
|
||||||
|
} else {
|
||||||
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case \App\Models\Service::class:
|
case Service::class:
|
||||||
StartService::run($resource);
|
StartService::run($resource);
|
||||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
break;
|
break;
|
||||||
@@ -333,6 +338,40 @@ class DeployController extends Controller
|
|||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Deployments'],
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'skip',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Number of records to skip.',
|
||||||
|
required: false,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'integer',
|
||||||
|
minimum: 0,
|
||||||
|
default: 0,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'take',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Number of records to take.',
|
||||||
|
required: false,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'integer',
|
||||||
|
minimum: 1,
|
||||||
|
default: 10,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
|
@@ -267,6 +267,18 @@ class ProjectController extends Controller
|
|||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Projects'],
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the project.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
requestBody: new OA\RequestBody(
|
requestBody: new OA\RequestBody(
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Project updated.',
|
description: 'Project updated.',
|
||||||
|
@@ -809,6 +809,6 @@ class ServersController extends Controller
|
|||||||
}
|
}
|
||||||
ValidateServer::dispatch($server);
|
ValidateServer::dispatch($server);
|
||||||
|
|
||||||
return response()->json(['message' => 'Validation started.']);
|
return response()->json(['message' => 'Validation started.'], 201);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -380,6 +380,9 @@ class ServicesController extends Controller
|
|||||||
|
|
||||||
$service = new Service;
|
$service = new Service;
|
||||||
$result = $this->upsert_service($request, $service, $teamId);
|
$result = $this->upsert_service($request, $service, $teamId);
|
||||||
|
if ($result instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($result))->setStatusCode(201);
|
return response()->json(serializeApiResponse($result))->setStatusCode(201);
|
||||||
} else {
|
} else {
|
||||||
@@ -527,6 +530,18 @@ class ServicesController extends Controller
|
|||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
tags: ['Services'],
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
requestBody: new OA\RequestBody(
|
requestBody: new OA\RequestBody(
|
||||||
description: 'Service updated.',
|
description: 'Service updated.',
|
||||||
required: true,
|
required: true,
|
||||||
@@ -596,12 +611,14 @@ class ServicesController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
if (! $service) {
|
if (! $service) {
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->upsert_service($request, $service, $teamId);
|
$result = $this->upsert_service($request, $service, $teamId);
|
||||||
|
if ($result instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($result))->setStatusCode(200);
|
return response()->json(serializeApiResponse($result))->setStatusCode(200);
|
||||||
}
|
}
|
||||||
|
@@ -100,18 +100,26 @@ class Bitbucket extends Controller
|
|||||||
if ($x_bitbucket_event === 'repo:push') {
|
if ($x_bitbucket_event === 'repo:push') {
|
||||||
if ($application->isDeployable()) {
|
if ($application->isDeployable()) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
commit: $commit,
|
commit: $commit,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true
|
is_webhook: true
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview deployment queued.',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Deployment queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
@@ -143,7 +151,7 @@ class Bitbucket extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -152,11 +160,19 @@ class Bitbucket extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'bitbucket'
|
git_type: 'bitbucket'
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview deployment queued.',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Preview deployment queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -116,19 +116,27 @@ class Gitea extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'status' => 'success',
|
$return_payloads->push([
|
||||||
'message' => 'Deployment queued.',
|
'application' => $application->name,
|
||||||
'application_uuid' => $application->uuid,
|
'status' => 'skipped',
|
||||||
'application_name' => $application->name,
|
'message' => $result['message'],
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Deployment queued.',
|
||||||
|
'application_uuid' => $application->uuid,
|
||||||
|
'application_name' => $application->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$paths = str($application->watch_paths)->explode("\n");
|
$paths = str($application->watch_paths)->explode("\n");
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
@@ -175,7 +183,7 @@ class Gitea extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -184,11 +192,19 @@ class Gitea extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'gitea'
|
git_type: 'gitea'
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview deployment queued.',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Preview deployment queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -122,19 +122,29 @@ class Github extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'status' => 'success',
|
$return_payloads->push([
|
||||||
'message' => 'Deployment queued.',
|
'application' => $application->name,
|
||||||
'application_uuid' => $application->uuid,
|
'status' => 'skipped',
|
||||||
'application_name' => $application->name,
|
'message' => $result['message'],
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Deployment queued.',
|
||||||
|
'application_uuid' => $application->uuid,
|
||||||
|
'application_name' => $application->name,
|
||||||
|
'deployment_uuid' => $result['deployment_uuid'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$paths = str($application->watch_paths)->explode("\n");
|
$paths = str($application->watch_paths)->explode("\n");
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
@@ -181,7 +191,8 @@ class Github extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
|
||||||
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -190,11 +201,19 @@ class Github extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview deployment queued.',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Preview deployment queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
@@ -341,7 +360,7 @@ class Github extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
@@ -349,10 +368,11 @@ class Github extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'status' => $result['status'],
|
||||||
'message' => 'Deployment queued.',
|
'message' => $result['message'],
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'application_name' => $application->name,
|
'application_name' => $application->name,
|
||||||
|
'deployment_uuid' => $result['deployment_uuid'],
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$paths = str($application->watch_paths)->explode("\n");
|
$paths = str($application->watch_paths)->explode("\n");
|
||||||
@@ -389,7 +409,7 @@ class Github extends Controller
|
|||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -398,11 +418,19 @@ class Github extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview deployment queued.',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Preview deployment queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -142,19 +142,28 @@ class Gitlab extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
commit: data_get($payload, 'after', 'HEAD'),
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'status' => 'success',
|
$return_payloads->push([
|
||||||
'message' => 'Deployment queued.',
|
'status' => $result['status'],
|
||||||
'application_uuid' => $application->uuid,
|
'message' => $result['message'],
|
||||||
'application_name' => $application->name,
|
'application_uuid' => $application->uuid,
|
||||||
]);
|
'application_name' => $application->name,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Deployment queued.',
|
||||||
|
'application_uuid' => $application->uuid,
|
||||||
|
'application_name' => $application->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$paths = str($application->watch_paths)->explode("\n");
|
$paths = str($application->watch_paths)->explode("\n");
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
@@ -201,7 +210,7 @@ class Gitlab extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -210,11 +219,19 @@ class Gitlab extends Controller
|
|||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'gitlab'
|
git_type: 'gitlab'
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
if ($result['status'] === 'skipped') {
|
||||||
'application' => $application->name,
|
$return_payloads->push([
|
||||||
'status' => 'success',
|
'application' => $application->name,
|
||||||
'message' => 'Preview Deployment queued',
|
'status' => 'skipped',
|
||||||
]);
|
'message' => $result['message'],
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$return_payloads->push([
|
||||||
|
'application' => $application->name,
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Preview Deployment queued',
|
||||||
|
]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
'application' => $application->name,
|
'application' => $application->name,
|
||||||
|
@@ -329,7 +329,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
|
$this->application_deployment_queue->addLogEntry("Gracefully shutting down build container: {$this->deployment_uuid}");
|
||||||
$this->graceful_shutdown_container($this->deployment_uuid);
|
$this->graceful_shutdown_container($this->deployment_uuid);
|
||||||
|
|
||||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||||
@@ -899,100 +899,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id');
|
$sorted_environment_variables_preview = $this->application->environment_variables_preview->sortBy('id');
|
||||||
}
|
}
|
||||||
$ports = $this->application->main_port();
|
$ports = $this->application->main_port();
|
||||||
if ($this->pull_request_id !== 0) {
|
$coolify_envs = $this->generate_coolify_env_variables();
|
||||||
$this->env_filename = ".env-pr-$this->pull_request_id";
|
$coolify_envs->each(function ($item, $key) use ($envs) {
|
||||||
// Add SOURCE_COMMIT if not exists
|
$envs->push($key.'='.$item);
|
||||||
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
|
});
|
||||||
if (! is_null($this->commit)) {
|
if ($this->pull_request_id === 0) {
|
||||||
$envs->push("SOURCE_COMMIT={$this->commit}");
|
|
||||||
} else {
|
|
||||||
$envs->push('SOURCE_COMMIT=unknown');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
|
|
||||||
$envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}");
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
|
|
||||||
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
|
||||||
$envs->push("COOLIFY_DOMAIN_FQDN={$url}");
|
|
||||||
}
|
|
||||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}");
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview);
|
|
||||||
|
|
||||||
foreach ($sorted_environment_variables_preview as $env) {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
if ($env->is_literal || $env->is_multiline) {
|
|
||||||
$real_value = '\''.$real_value.'\'';
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$envs->push($env->key.'='.$real_value);
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($this->build_pack !== 'dockercompose') {
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
|
||||||
$envs->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add HOST if not exists
|
|
||||||
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
|
||||||
$envs->push('HOST=0.0.0.0');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->env_filename = '.env';
|
$this->env_filename = '.env';
|
||||||
// Add SOURCE_COMMIT if not exists
|
|
||||||
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
|
|
||||||
if (! is_null($this->commit)) {
|
|
||||||
$envs->push("SOURCE_COMMIT={$this->commit}");
|
|
||||||
} else {
|
|
||||||
$envs->push('SOURCE_COMMIT=unknown');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
|
||||||
if ((int) $this->application->compose_parsing_version >= 3) {
|
|
||||||
$envs->push("COOLIFY_URL={$this->application->fqdn}");
|
|
||||||
} else {
|
|
||||||
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
|
||||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
|
||||||
if ((int) $this->application->compose_parsing_version >= 3) {
|
|
||||||
$envs->push("COOLIFY_FQDN={$url}");
|
|
||||||
} else {
|
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_RESOURCE_UUID={$this->application->uuid}");
|
|
||||||
}
|
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
|
||||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables);
|
|
||||||
|
|
||||||
foreach ($sorted_environment_variables as $env) {
|
foreach ($sorted_environment_variables as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
@@ -1017,6 +929,32 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
||||||
$envs->push('HOST=0.0.0.0');
|
$envs->push('HOST=0.0.0.0');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$this->env_filename = ".env-pr-$this->pull_request_id";
|
||||||
|
foreach ($sorted_environment_variables_preview as $env) {
|
||||||
|
$real_value = $env->real_value;
|
||||||
|
if ($env->version === '4.0.0-beta.239') {
|
||||||
|
$real_value = $env->real_value;
|
||||||
|
} else {
|
||||||
|
if ($env->is_literal || $env->is_multiline) {
|
||||||
|
$real_value = '\''.$real_value.'\'';
|
||||||
|
} else {
|
||||||
|
$real_value = escapeEnvVariables($env->real_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$envs->push($env->key.'='.$real_value);
|
||||||
|
}
|
||||||
|
// Add PORT if not exists, use the first port as default
|
||||||
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
||||||
|
$envs->push("PORT={$ports[0]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add HOST if not exists
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
||||||
|
$envs->push('HOST=0.0.0.0');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if ($envs->isEmpty()) {
|
if ($envs->isEmpty()) {
|
||||||
$this->env_filename = null;
|
$this->env_filename = null;
|
||||||
@@ -1361,7 +1299,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
|
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
|
||||||
$this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
|
|
||||||
$this->graceful_shutdown_container($this->deployment_uuid);
|
$this->graceful_shutdown_container($this->deployment_uuid);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -1394,6 +1331,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
foreach ($destination_ids as $destination_id) {
|
foreach ($destination_ids as $destination_id) {
|
||||||
$destination = StandaloneDocker::find($destination_id);
|
$destination = StandaloneDocker::find($destination_id);
|
||||||
|
if (! $destination) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$server = $destination->server;
|
$server = $destination->server;
|
||||||
if ($server->team_id !== $this->mainServer->team_id) {
|
if ($server->team_id !== $this->mainServer->team_id) {
|
||||||
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
||||||
@@ -1437,6 +1377,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function check_git_if_build_needed()
|
private function check_git_if_build_needed()
|
||||||
{
|
{
|
||||||
|
if ($this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) {
|
||||||
|
$repository = githubApi($this->source, "repos/{$this->customRepository}");
|
||||||
|
$data = data_get($repository, 'data');
|
||||||
|
if (isset($data->id)) {
|
||||||
|
$repository_project_id = $data->id;
|
||||||
|
if (blank($this->application->repository_project_id) || $this->application->repository_project_id !== $repository_project_id) {
|
||||||
|
$this->application->repository_project_id = $repository_project_id;
|
||||||
|
$this->application->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->generate_git_import_commands();
|
$this->generate_git_import_commands();
|
||||||
$local_branch = $this->branch;
|
$local_branch = $this->branch;
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -1626,20 +1577,128 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
|
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generate_coolify_env_variables(): Collection
|
||||||
|
{
|
||||||
|
$coolify_envs = collect([]);
|
||||||
|
$local_branch = $this->branch;
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
// Add SOURCE_COMMIT if not exists
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
|
||||||
|
if (! is_null($this->commit)) {
|
||||||
|
$coolify_envs->put('SOURCE_COMMIT', $this->commit);
|
||||||
|
} else {
|
||||||
|
$coolify_envs->put('SOURCE_COMMIT', 'unknown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_FQDN', $this->preview->fqdn);
|
||||||
|
$coolify_envs->put('COOLIFY_DOMAIN_URL', $this->preview->fqdn);
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
|
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
$coolify_envs->put('COOLIFY_URL', $url);
|
||||||
|
$coolify_envs->put('COOLIFY_DOMAIN_FQDN', $url);
|
||||||
|
}
|
||||||
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_BRANCH', $local_branch);
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid);
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->application, $coolify_envs, $this->application->environment_variables_preview);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Add SOURCE_COMMIT if not exists
|
||||||
|
if ($this->application->environment_variables->where('key', 'SOURCE_COMMIT')->isEmpty()) {
|
||||||
|
if (! is_null($this->commit)) {
|
||||||
|
$coolify_envs->put('SOURCE_COMMIT', $this->commit);
|
||||||
|
} else {
|
||||||
|
$coolify_envs->put('SOURCE_COMMIT', 'unknown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
|
if ((int) $this->application->compose_parsing_version >= 3) {
|
||||||
|
$coolify_envs->put('COOLIFY_URL', $this->application->fqdn);
|
||||||
|
} else {
|
||||||
|
$coolify_envs->put('COOLIFY_FQDN', $this->application->fqdn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
|
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
if ((int) $this->application->compose_parsing_version >= 3) {
|
||||||
|
$coolify_envs->put('COOLIFY_FQDN', $url);
|
||||||
|
} else {
|
||||||
|
$coolify_envs->put('COOLIFY_URL', $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_BRANCH', $local_branch);
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_RESOURCE_UUID')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_RESOURCE_UUID', $this->application->uuid);
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$coolify_envs->put('COOLIFY_CONTAINER_NAME', $this->container_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_coolify_default_environment_variables($this->application, $coolify_envs, $this->application->environment_variables);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $coolify_envs;
|
||||||
|
}
|
||||||
|
|
||||||
private function generate_env_variables()
|
private function generate_env_variables()
|
||||||
{
|
{
|
||||||
$this->env_args = collect([]);
|
$this->env_args = collect([]);
|
||||||
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
||||||
|
$coolify_envs = $this->generate_coolify_env_variables();
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
foreach ($this->application->build_environment_variables as $env) {
|
foreach ($this->application->build_environment_variables as $env) {
|
||||||
if (! is_null($env->real_value)) {
|
if (! is_null($env->real_value)) {
|
||||||
$this->env_args->put($env->key, $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 {
|
} else {
|
||||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||||
if (! is_null($env->real_value)) {
|
if (! is_null($env->real_value)) {
|
||||||
$this->env_args->put($env->key, $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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1664,25 +1723,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$labels = $labels->filter(function ($value, $key) {
|
$labels = $labels->filter(function ($value, $key) {
|
||||||
return ! Str::startsWith($value, 'coolify.');
|
return ! Str::startsWith($value, 'coolify.');
|
||||||
});
|
});
|
||||||
$found_caddy_labels = $labels->filter(function ($value, $key) {
|
|
||||||
return Str::startsWith($value, 'caddy_');
|
|
||||||
});
|
|
||||||
if ($found_caddy_labels->count() === 0) {
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$domains = str(data_get($this->preview, 'fqdn'))->explode(',');
|
|
||||||
} else {
|
|
||||||
$domains = str(data_get($this->application, 'fqdn'))->explode(',');
|
|
||||||
}
|
|
||||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
|
||||||
network: $this->application->destination->network,
|
|
||||||
uuid: $this->application->uuid,
|
|
||||||
domains: $domains,
|
|
||||||
onlyPort: $onlyPort,
|
|
||||||
is_force_https_enabled: $this->application->isForceHttpsEnabled(),
|
|
||||||
is_gzip_enabled: $this->application->isGzipEnabled(),
|
|
||||||
is_stripprefix_enabled: $this->application->isStripprefixEnabled()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
} else {
|
} else {
|
||||||
@@ -1710,6 +1750,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
]);
|
]);
|
||||||
$this->application->parseHealthcheckFromDockerfile($this->saved_outputs->get('dockerfile_from_repo'));
|
$this->application->parseHealthcheckFromDockerfile($this->saved_outputs->get('dockerfile_from_repo'));
|
||||||
}
|
}
|
||||||
|
$custom_network_aliases = [];
|
||||||
|
if (is_array($this->application->custom_network_aliases) && count($this->application->custom_network_aliases) > 0) {
|
||||||
|
$custom_network_aliases = $this->application->custom_network_aliases;
|
||||||
|
}
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$this->container_name => [
|
$this->container_name => [
|
||||||
@@ -1719,9 +1763,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network => [
|
$this->destination->network => [
|
||||||
'aliases' => [
|
'aliases' => array_merge(
|
||||||
$this->container_name,
|
[$this->container_name],
|
||||||
],
|
$custom_network_aliases
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->application->limits_memory,
|
'mem_limit' => $this->application->limits_memory,
|
||||||
@@ -2409,20 +2454,23 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
private function next(string $status)
|
private function next(string $status)
|
||||||
{
|
{
|
||||||
queue_next_deployment($this->application);
|
queue_next_deployment($this->application);
|
||||||
// If the deployment is cancelled by the user, don't update the status
|
|
||||||
if (
|
// Never allow changing status from FAILED or CANCELLED_BY_USER to anything else
|
||||||
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value &&
|
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value ||
|
||||||
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
|
$this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||||
) {
|
return;
|
||||||
$this->application_deployment_queue->update([
|
|
||||||
'status' => $status,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) {
|
|
||||||
|
$this->application_deployment_queue->update([
|
||||||
|
'status' => $status,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||||
if (! $this->only_this_server) {
|
if (! $this->only_this_server) {
|
||||||
$this->deploy_to_additional_destinations();
|
$this->deploy_to_additional_destinations();
|
||||||
|
@@ -17,11 +17,13 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 60;
|
||||||
|
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()];
|
return [(new WithoutOverlapping('cleanup-instance-stuffs'))->expireAfter(60)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
|
@@ -31,7 +31,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->expireAfter(600)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||||
|
@@ -71,7 +71,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->expireAfter(30)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function backoff(): int
|
public function backoff(): int
|
||||||
|
@@ -24,7 +24,7 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
@@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
@@ -269,7 +269,7 @@ class Email extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->smtpEnabled = false;
|
$this->smtpEnabled = false;
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,32 +337,29 @@ class Email extends Component
|
|||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
$this->smtpFromAddress = $settings->smtp_from_address;
|
||||||
|
$this->smtpFromName = $settings->smtp_from_name;
|
||||||
|
|
||||||
if ($settings->smtp_enabled) {
|
if ($settings->smtp_enabled) {
|
||||||
$this->smtpEnabled = true;
|
$this->smtpEnabled = true;
|
||||||
$this->smtpFromAddress = $settings->smtp_from_address;
|
|
||||||
$this->smtpFromName = $settings->smtp_from_name;
|
|
||||||
$this->smtpRecipients = $settings->smtp_recipients;
|
|
||||||
$this->smtpHost = $settings->smtp_host;
|
|
||||||
$this->smtpPort = $settings->smtp_port;
|
|
||||||
$this->smtpEncryption = $settings->smtp_encryption;
|
|
||||||
$this->smtpUsername = $settings->smtp_username;
|
|
||||||
$this->smtpPassword = $settings->smtp_password;
|
|
||||||
$this->smtpTimeout = $settings->smtp_timeout;
|
|
||||||
$this->resendEnabled = false;
|
$this->resendEnabled = false;
|
||||||
$this->saveModel();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->smtpRecipients = $settings->smtp_recipients;
|
||||||
|
$this->smtpHost = $settings->smtp_host;
|
||||||
|
$this->smtpPort = $settings->smtp_port;
|
||||||
|
$this->smtpEncryption = $settings->smtp_encryption;
|
||||||
|
$this->smtpUsername = $settings->smtp_username;
|
||||||
|
$this->smtpPassword = $settings->smtp_password;
|
||||||
|
$this->smtpTimeout = $settings->smtp_timeout;
|
||||||
|
|
||||||
if ($settings->resend_enabled) {
|
if ($settings->resend_enabled) {
|
||||||
$this->resendEnabled = true;
|
$this->resendEnabled = true;
|
||||||
$this->resendApiKey = $settings->resend_api_key;
|
|
||||||
$this->smtpEnabled = false;
|
$this->smtpEnabled = false;
|
||||||
$this->saveModel();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
$this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
|
$this->resendApiKey = $settings->resend_api_key;
|
||||||
|
$this->saveModel();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
@@ -68,6 +68,7 @@ class General extends Component
|
|||||||
'application.publish_directory' => 'nullable',
|
'application.publish_directory' => 'nullable',
|
||||||
'application.ports_exposes' => 'required',
|
'application.ports_exposes' => 'required',
|
||||||
'application.ports_mappings' => 'nullable',
|
'application.ports_mappings' => 'nullable',
|
||||||
|
'application.custom_network_aliases' => 'nullable',
|
||||||
'application.dockerfile' => 'nullable',
|
'application.dockerfile' => 'nullable',
|
||||||
'application.docker_registry_image_name' => 'nullable',
|
'application.docker_registry_image_name' => 'nullable',
|
||||||
'application.docker_registry_image_tag' => 'nullable',
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
@@ -93,6 +94,9 @@ class General extends Component
|
|||||||
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
|
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
|
||||||
'application.watch_paths' => 'nullable',
|
'application.watch_paths' => 'nullable',
|
||||||
'application.redirect' => 'string|required',
|
'application.redirect' => 'string|required',
|
||||||
|
'application.http_basic_auth_enabled' => 'boolean|required',
|
||||||
|
'application.http_basic_auth_username' => 'nullable',
|
||||||
|
'application.http_basic_auth_password' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -121,6 +125,7 @@ class General extends Component
|
|||||||
'application.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||||
|
'application.custom_network_aliases' => 'Custom docker network aliases',
|
||||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||||
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
||||||
@@ -455,7 +460,6 @@ class General extends Component
|
|||||||
{
|
{
|
||||||
$config = GenerateConfig::run($this->application, true);
|
$config = GenerateConfig::run($this->application, true);
|
||||||
$fileName = str($this->application->name)->slug()->append('_config.json');
|
$fileName = str($this->application->name)->slug()->append('_config.json');
|
||||||
dd($config);
|
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($config) {
|
return response()->streamDownload(function () use ($config) {
|
||||||
echo $config;
|
echo $config;
|
||||||
|
@@ -84,11 +84,16 @@ class Heading extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->setDeploymentUuid();
|
$this->setDeploymentUuid();
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
force_rebuild: $force_rebuild,
|
force_rebuild: $force_rebuild,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectRoute('project.application.deployment.show', [
|
return $this->redirectRoute('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
@@ -126,11 +131,16 @@ class Heading extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->setDeploymentUuid();
|
$this->setDeploymentUuid();
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
restart_only: true,
|
restart_only: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectRoute('project.application.deployment.show', [
|
return $this->redirectRoute('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
|
@@ -159,13 +159,18 @@ class Previews extends Component
|
|||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
deployment_uuid: $this->deployment_uuid,
|
deployment_uuid: $this->deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
git_type: $found->git_type ?? null,
|
git_type: $found->git_type ?? null,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
return redirect()->route('project.application.deployment.show', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
|
@@ -30,11 +30,15 @@ class Source extends Component
|
|||||||
#[Validate(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $gitCommitSha = null;
|
public ?string $gitCommitSha = null;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public $sources;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
$this->getPrivateKeys();
|
$this->getPrivateKeys();
|
||||||
|
$this->getSources();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
handleError($e, $this);
|
handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -66,6 +70,14 @@ class Source extends Component
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getSources()
|
||||||
|
{
|
||||||
|
// filter the current source out
|
||||||
|
$this->sources = currentTeam()->sources()->whereNotNull('app_id')->reject(function ($source) {
|
||||||
|
return $source->id === $this->application->source_id;
|
||||||
|
})->sortBy('name');
|
||||||
|
}
|
||||||
|
|
||||||
public function setPrivateKey(int $privateKeyId)
|
public function setPrivateKey(int $privateKeyId)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -92,4 +104,20 @@ class Source extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function changeSource($sourceId, $sourceType)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->application->update([
|
||||||
|
'source_id' => $sourceId,
|
||||||
|
'source_type' => $sourceType,
|
||||||
|
'repository_project_id' => null,
|
||||||
|
]);
|
||||||
|
$this->application->refresh();
|
||||||
|
$this->getSources();
|
||||||
|
$this->dispatch('success', 'Source updated!');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ class General extends Component
|
|||||||
|
|
||||||
public string $redis_username;
|
public string $redis_username;
|
||||||
|
|
||||||
public string $redis_password;
|
public ?string $redis_password;
|
||||||
|
|
||||||
public string $redis_version;
|
public string $redis_version;
|
||||||
|
|
||||||
|
@@ -79,7 +79,7 @@ class Destination extends Component
|
|||||||
$deployment_uuid = new Cuid2;
|
$deployment_uuid = new Cuid2;
|
||||||
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
||||||
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
|
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
|
||||||
queue_application_deployment(
|
$result = queue_application_deployment(
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
application: $this->resource,
|
application: $this->resource,
|
||||||
server: $server,
|
server: $server,
|
||||||
@@ -87,6 +87,11 @@ class Destination extends Component
|
|||||||
only_this_server: true,
|
only_this_server: true,
|
||||||
no_questions_asked: true,
|
no_questions_asked: true,
|
||||||
);
|
);
|
||||||
|
if ($result['status'] === 'skipped') {
|
||||||
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
return redirect()->route('project.application.deployment.show', [
|
||||||
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
||||||
|
@@ -3,10 +3,13 @@
|
|||||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||||
|
|
||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Traits\EnvironmentVariableProtection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
|
use EnvironmentVariableProtection;
|
||||||
|
|
||||||
public $resource;
|
public $resource;
|
||||||
|
|
||||||
public string $resourceClass;
|
public string $resourceClass;
|
||||||
@@ -138,17 +141,57 @@ class All extends Component
|
|||||||
private function handleBulkSubmit()
|
private function handleBulkSubmit()
|
||||||
{
|
{
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
|
$changesMade = false;
|
||||||
|
$errorOccurred = false;
|
||||||
|
|
||||||
$this->deleteRemovedVariables(false, $variables);
|
// Try to delete removed variables
|
||||||
$this->updateOrCreateVariables(false, $variables);
|
$deletedCount = $this->deleteRemovedVariables(false, $variables);
|
||||||
|
if ($deletedCount > 0) {
|
||||||
|
$changesMade = true;
|
||||||
|
} elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) {
|
||||||
|
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||||
|
$errorOccurred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update or create variables
|
||||||
|
$updatedCount = $this->updateOrCreateVariables(false, $variables);
|
||||||
|
if ($updatedCount > 0) {
|
||||||
|
$changesMade = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->showPreview) {
|
if ($this->showPreview) {
|
||||||
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
|
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
|
||||||
$this->deleteRemovedVariables(true, $previewVariables);
|
|
||||||
$this->updateOrCreateVariables(true, $previewVariables);
|
// Try to delete removed preview variables
|
||||||
|
$deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables);
|
||||||
|
if ($deletedPreviewCount > 0) {
|
||||||
|
$changesMade = true;
|
||||||
|
} elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) {
|
||||||
|
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||||
|
$errorOccurred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update or create preview variables
|
||||||
|
$updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables);
|
||||||
|
if ($updatedPreviewCount > 0) {
|
||||||
|
$changesMade = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->dispatch('success', 'Environment variables updated.');
|
// Debug information
|
||||||
|
\Log::info('Environment variables update status', [
|
||||||
|
'deletedCount' => $deletedCount,
|
||||||
|
'updatedCount' => $updatedCount,
|
||||||
|
'deletedPreviewCount' => $deletedPreviewCount ?? 0,
|
||||||
|
'updatedPreviewCount' => $updatedPreviewCount ?? 0,
|
||||||
|
'changesMade' => $changesMade,
|
||||||
|
'errorOccurred' => $errorOccurred,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Only show success message if changes were actually made and no errors occurred
|
||||||
|
if ($changesMade && ! $errorOccurred) {
|
||||||
|
$this->dispatch('success', 'Environment variables updated.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleSingleSubmit($data)
|
private function handleSingleSubmit($data)
|
||||||
@@ -184,11 +227,37 @@ class All extends Component
|
|||||||
private function deleteRemovedVariables($isPreview, $variables)
|
private function deleteRemovedVariables($isPreview, $variables)
|
||||||
{
|
{
|
||||||
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
||||||
|
|
||||||
|
// Get all environment variables that will be deleted
|
||||||
|
$variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get();
|
||||||
|
|
||||||
|
// If there are no variables to delete, return 0
|
||||||
|
if ($variablesToDelete->isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of these variables are used in Docker Compose
|
||||||
|
if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') {
|
||||||
|
foreach ($variablesToDelete as $envVar) {
|
||||||
|
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose);
|
||||||
|
|
||||||
|
if ($isUsed) {
|
||||||
|
$this->dispatch('error', "Cannot delete environment variable '{$envVar->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, no variables are used in Docker Compose, so we can delete them
|
||||||
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
|
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
|
||||||
|
|
||||||
|
return $variablesToDelete->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateOrCreateVariables($isPreview, $variables)
|
private function updateOrCreateVariables($isPreview, $variables)
|
||||||
{
|
{
|
||||||
|
$count = 0;
|
||||||
foreach ($variables as $key => $value) {
|
foreach ($variables as $key => $value) {
|
||||||
if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) {
|
if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) {
|
||||||
continue;
|
continue;
|
||||||
@@ -198,8 +267,12 @@ class All extends Component
|
|||||||
|
|
||||||
if ($found) {
|
if ($found) {
|
||||||
if (! $found->is_shown_once && ! $found->is_multiline) {
|
if (! $found->is_shown_once && ! $found->is_multiline) {
|
||||||
$found->value = $value;
|
// Only count as a change if the value actually changed
|
||||||
$found->save();
|
if ($found->value !== $value) {
|
||||||
|
$found->value = $value;
|
||||||
|
$found->save();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$environment = new EnvironmentVariable;
|
$environment = new EnvironmentVariable;
|
||||||
@@ -212,8 +285,11 @@ class All extends Component
|
|||||||
$environment->resourceable_type = $this->resource->getMorphClass();
|
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||||
|
|
||||||
$environment->save();
|
$environment->save();
|
||||||
|
$count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refreshEnvs()
|
public function refreshEnvs()
|
||||||
|
@@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
|||||||
|
|
||||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||||
use App\Models\SharedEnvironmentVariable;
|
use App\Models\SharedEnvironmentVariable;
|
||||||
|
use App\Traits\EnvironmentVariableProtection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
|
use EnvironmentVariableProtection;
|
||||||
|
|
||||||
public $parameters;
|
public $parameters;
|
||||||
|
|
||||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||||
@@ -40,6 +43,8 @@ class Show extends Component
|
|||||||
|
|
||||||
public bool $is_really_required = false;
|
public bool $is_really_required = false;
|
||||||
|
|
||||||
|
public bool $is_redis_credential = false;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'refreshEnvs' => 'refresh',
|
'refreshEnvs' => 'refresh',
|
||||||
'refresh',
|
'refresh',
|
||||||
@@ -65,7 +70,9 @@ class Show extends Component
|
|||||||
}
|
}
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->checkEnvs();
|
$this->checkEnvs();
|
||||||
|
if ($this->type === 'standalone-redis' && ($this->env->key === 'REDIS_PASSWORD' || $this->env->key === 'REDIS_USERNAME')) {
|
||||||
|
$this->is_redis_credential = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refresh()
|
public function refresh()
|
||||||
@@ -171,6 +178,17 @@ class Show extends Component
|
|||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Check if the variable is used in Docker Compose
|
||||||
|
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) {
|
||||||
|
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);
|
||||||
|
|
||||||
|
if ($isUsed) {
|
||||||
|
$this->dispatch('error', "Cannot delete environment variable '{$this->env->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->env->delete();
|
$this->env->delete();
|
||||||
$this->dispatch('environmentVariableDeleted');
|
$this->dispatch('environmentVariableDeleted');
|
||||||
$this->dispatch('success', 'Environment variable deleted successfully.');
|
$this->dispatch('success', 'Environment variable deleted successfully.');
|
||||||
|
@@ -38,7 +38,8 @@ class DynamicConfigurations extends Component
|
|||||||
$contents = collect([]);
|
$contents = collect([]);
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$without_extension = str_replace('.', '|', $file);
|
$without_extension = str_replace('.', '|', $file);
|
||||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
$content = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||||
|
$contents[$without_extension] = $content ?? '';
|
||||||
}
|
}
|
||||||
$this->contents = $contents;
|
$this->contents = $contents;
|
||||||
$this->dispatch('$refresh');
|
$this->dispatch('$refresh');
|
||||||
|
@@ -177,7 +177,7 @@ class SettingsEmail extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->smtpEnabled = false;
|
$this->smtpEnabled = false;
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ class SettingsEmail extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->resendEnabled = false;
|
$this->resendEnabled = false;
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,19 +12,30 @@ class Index extends Component
|
|||||||
|
|
||||||
public bool $alreadySubscribed = false;
|
public bool $alreadySubscribed = false;
|
||||||
|
|
||||||
|
public bool $isUnpaid = false;
|
||||||
|
|
||||||
|
public bool $isCancelled = false;
|
||||||
|
|
||||||
|
public bool $isMember = false;
|
||||||
|
|
||||||
|
public bool $loading = true;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
if (auth()->user()?->isMember()) {
|
if (auth()->user()?->isMember()) {
|
||||||
return redirect()->route('dashboard');
|
$this->isMember = true;
|
||||||
}
|
}
|
||||||
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
||||||
return redirect()->route('subscription.show');
|
return redirect()->route('subscription.show');
|
||||||
}
|
}
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||||
|
if (! $this->alreadySubscribed) {
|
||||||
|
$this->loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stripeCustomerPortal()
|
public function stripeCustomerPortal()
|
||||||
@@ -37,6 +48,41 @@ class Index extends Component
|
|||||||
return redirect($session->url);
|
return redirect($session->url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getStripeStatus()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$subscription = currentTeam()->subscription;
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id);
|
||||||
|
if ($customer) {
|
||||||
|
$subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]);
|
||||||
|
$currentTeam = currentTeam()->id ?? null;
|
||||||
|
if (count($subscriptions->data) > 0 && $currentTeam) {
|
||||||
|
$foundSubscription = collect($subscriptions->data)->firstWhere('metadata.team_id', $currentTeam);
|
||||||
|
if ($foundSubscription) {
|
||||||
|
$status = data_get($foundSubscription, 'status');
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_subscription_id' => $foundSubscription->id,
|
||||||
|
]);
|
||||||
|
if ($status === 'unpaid') {
|
||||||
|
$this->isUnpaid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($subscriptions->data) === 0) {
|
||||||
|
$this->isCancelled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Log the error
|
||||||
|
logger()->error('Stripe API error: ' . $e->getMessage());
|
||||||
|
// Set a flag to show an error message to the user
|
||||||
|
$this->addError('stripe', 'Could not retrieve subscription information. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
$this->loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.subscription.index');
|
return view('livewire.subscription.index');
|
||||||
|
@@ -45,6 +45,7 @@ use Visus\Cuid2\Cuid2;
|
|||||||
'start_command' => ['type' => 'string', 'description' => 'Start command.'],
|
'start_command' => ['type' => 'string', 'description' => 'Start command.'],
|
||||||
'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'],
|
'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'],
|
||||||
'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'],
|
'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'],
|
||||||
|
'custom_network_aliases' => ['type' => 'string', 'nullable' => true, 'description' => 'Network aliases for Docker container.'],
|
||||||
'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'],
|
'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'],
|
||||||
'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'],
|
'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'],
|
||||||
'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
|
'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
|
||||||
@@ -102,6 +103,9 @@ use Visus\Cuid2\Cuid2;
|
|||||||
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
||||||
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
|
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
|
||||||
'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'],
|
'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'],
|
||||||
|
'http_basic_auth_enabled' => ['type' => 'boolean', 'description' => 'HTTP Basic Authentication enabled.'],
|
||||||
|
'http_basic_auth_username' => ['type' => 'string', 'nullable' => true, 'description' => 'Username for HTTP Basic Authentication'],
|
||||||
|
'http_basic_auth_password' => ['type' => 'string', 'nullable' => true, 'description' => 'Password for HTTP Basic Authentication'],
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
@@ -115,6 +119,68 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
protected $appends = ['server_status'];
|
protected $appends = ['server_status'];
|
||||||
|
|
||||||
|
protected $casts = ['custom_network_aliases' => 'array'];
|
||||||
|
|
||||||
|
public function customNetworkAliases(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (is_null($value) || $value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's already a JSON string, decode it
|
||||||
|
if (is_string($value) && $this->isJson($value)) {
|
||||||
|
$value = json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a string but not JSON, treat it as a comma-separated list
|
||||||
|
if (is_string($value) && ! is_array($value)) {
|
||||||
|
$value = explode(',', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = collect($value)
|
||||||
|
->map(function ($alias) {
|
||||||
|
if (is_string($alias)) {
|
||||||
|
return str_replace(' ', '-', trim($alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
->filter()
|
||||||
|
->unique() // Remove duplicate values
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return empty($value) ? null : json_encode($value);
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value) && $this->isJson($value)) {
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($value) ? $value : [];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid JSON
|
||||||
|
*/
|
||||||
|
private function isJson($string)
|
||||||
|
{
|
||||||
|
if (! is_string($string)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
json_decode($string);
|
||||||
|
|
||||||
|
return json_last_error() === JSON_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::addGlobalScope('withRelations', function ($builder) {
|
static::addGlobalScope('withRelations', function ($builder) {
|
||||||
@@ -392,22 +458,23 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: function () {
|
get: function () {
|
||||||
|
$base_dir = $this->base_directory ?? '/';
|
||||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||||
if (str($this->git_repository)->contains('bitbucket')) {
|
if (str($this->git_repository)->contains('bitbucket')) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/src/{$this->git_branch}{$base_dir}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}{$base_dir}";
|
||||||
}
|
}
|
||||||
// Convert the SSH URL to HTTPS URL
|
// Convert the SSH URL to HTTPS URL
|
||||||
if (strpos($this->git_repository, 'git@') === 0) {
|
if (strpos($this->git_repository, 'git@') === 0) {
|
||||||
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||||
|
|
||||||
if (str($this->git_repository)->contains('bitbucket')) {
|
if (str($this->git_repository)->contains('bitbucket')) {
|
||||||
return "https://{$git_repository}/src/{$this->git_branch}";
|
return "https://{$git_repository}/src/{$this->git_branch}{$base_dir}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "https://{$git_repository}/tree/{$this->git_branch}";
|
return "https://{$git_repository}/tree/{$this->git_branch}{$base_dir}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->git_repository;
|
return $this->git_repository;
|
||||||
|
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
@@ -101,17 +102,23 @@ class ApplicationDeploymentQueue extends Model
|
|||||||
'hidden' => $hidden,
|
'hidden' => $hidden,
|
||||||
'batch' => 1,
|
'batch' => 1,
|
||||||
];
|
];
|
||||||
if ($this->logs) {
|
|
||||||
$previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
// Use a transaction to ensure atomicity
|
||||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
DB::transaction(function () use ($newLogEntry) {
|
||||||
$previousLogs[] = $newLogEntry;
|
// Reload the model to get the latest logs
|
||||||
$this->update([
|
$this->refresh();
|
||||||
'logs' => json_encode($previousLogs, flags: JSON_THROW_ON_ERROR),
|
|
||||||
]);
|
if ($this->logs) {
|
||||||
} else {
|
$previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||||
$this->update([
|
$newLogEntry['order'] = count($previousLogs) + 1;
|
||||||
'logs' => json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR),
|
$previousLogs[] = $newLogEntry;
|
||||||
]);
|
$this->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
||||||
}
|
} else {
|
||||||
|
$this->logs = json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save without triggering events to prevent potential race conditions
|
||||||
|
$this->saveQuietly();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,8 @@ use phpseclib3\Crypt\PublicKeyLoader;
|
|||||||
'name' => ['type' => 'string'],
|
'name' => ['type' => 'string'],
|
||||||
'description' => ['type' => 'string'],
|
'description' => ['type' => 'string'],
|
||||||
'private_key' => ['type' => 'string', 'format' => 'private-key'],
|
'private_key' => ['type' => 'string', 'format' => 'private-key'],
|
||||||
|
'public_key' => ['type' => 'string'],
|
||||||
|
'fingerprint' => ['type' => 'string'],
|
||||||
'is_git_related' => ['type' => 'boolean'],
|
'is_git_related' => ['type' => 'boolean'],
|
||||||
'team_id' => ['type' => 'integer'],
|
'team_id' => ['type' => 'integer'],
|
||||||
'created_at' => ['type' => 'string'],
|
'created_at' => ['type' => 'string'],
|
||||||
|
@@ -20,7 +20,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Stringable;
|
use Illuminate\Support\Stringable;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
@@ -493,11 +492,7 @@ $schema://$host {
|
|||||||
if ($proxyType === ProxyTypes::TRAEFIK->value) {
|
if ($proxyType === ProxyTypes::TRAEFIK->value) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
} elseif ($proxyType === ProxyTypes::CADDY->value) {
|
} elseif ($proxyType === ProxyTypes::CADDY->value) {
|
||||||
if (isDev()) {
|
$proxy_path = $proxy_path.'/caddy';
|
||||||
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
|
|
||||||
} else {
|
|
||||||
$proxy_path = $proxy_path.'/caddy';
|
|
||||||
}
|
|
||||||
} elseif ($proxyType === ProxyTypes::NGINX->value) {
|
} elseif ($proxyType === ProxyTypes::NGINX->value) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
|
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
|
||||||
@@ -925,7 +920,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
{
|
{
|
||||||
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
|
$isFunctional = data_get($this->settings, 'is_reachable') && data_get($this->settings, 'is_usable') && data_get($this->settings, 'force_disabled') === false && $this->ip !== '1.2.3.4';
|
||||||
|
|
||||||
if ($isFunctional === false) {
|
if ($isFunctional === false) {
|
||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
@@ -1026,22 +1021,11 @@ $schema://$host {
|
|||||||
$this->refresh();
|
$this->refresh();
|
||||||
$unreachableNotificationSent = (bool) $this->unreachable_notification_sent;
|
$unreachableNotificationSent = (bool) $this->unreachable_notification_sent;
|
||||||
$isReachable = (bool) $this->settings->is_reachable;
|
$isReachable = (bool) $this->settings->is_reachable;
|
||||||
|
|
||||||
Log::debug('Server reachability check', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
'is_reachable' => $isReachable,
|
|
||||||
'notification_sent' => $unreachableNotificationSent,
|
|
||||||
'unreachable_count' => $this->unreachable_count,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($isReachable === true) {
|
if ($isReachable === true) {
|
||||||
$this->unreachable_count = 0;
|
$this->unreachable_count = 0;
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|
||||||
if ($unreachableNotificationSent === true) {
|
if ($unreachableNotificationSent === true) {
|
||||||
Log::debug('Server is now reachable, sending notification', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
$this->sendReachableNotification();
|
$this->sendReachableNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,17 +1033,10 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->increment('unreachable_count');
|
$this->increment('unreachable_count');
|
||||||
Log::debug('Incremented unreachable count', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
'new_count' => $this->unreachable_count,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->unreachable_count === 1) {
|
if ($this->unreachable_count === 1) {
|
||||||
$this->settings->is_reachable = true;
|
$this->settings->is_reachable = true;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
Log::debug('First unreachable attempt, marking as reachable', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1068,11 +1045,6 @@ $schema://$host {
|
|||||||
$failedChecks = 0;
|
$failedChecks = 0;
|
||||||
for ($i = 0; $i < 3; $i++) {
|
for ($i = 0; $i < 3; $i++) {
|
||||||
$status = $this->serverStatus();
|
$status = $this->serverStatus();
|
||||||
Log::debug('Additional reachability check', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
'attempt' => $i + 1,
|
|
||||||
'status' => $status,
|
|
||||||
]);
|
|
||||||
sleep(5);
|
sleep(5);
|
||||||
if (! $status) {
|
if (! $status) {
|
||||||
$failedChecks++;
|
$failedChecks++;
|
||||||
@@ -1080,9 +1052,6 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($failedChecks === 3 && ! $unreachableNotificationSent) {
|
if ($failedChecks === 3 && ! $unreachableNotificationSent) {
|
||||||
Log::debug('Server confirmed unreachable after 3 attempts, sending notification', [
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
$this->sendUnreachableNotification();
|
$this->sendUnreachableNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -141,6 +141,6 @@ class ServiceDatabase extends BaseModel
|
|||||||
str($this->databaseType())->contains('postgres') ||
|
str($this->databaseType())->contains('postgres') ||
|
||||||
str($this->databaseType())->contains('postgis') ||
|
str($this->databaseType())->contains('postgis') ||
|
||||||
str($this->databaseType())->contains('mariadb') ||
|
str($this->databaseType())->contains('mariadb') ||
|
||||||
str($this->databaseType())->contains('mongodb');
|
str($this->databaseType())->contains('mongo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -192,8 +192,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
|||||||
public function subscriptionEnded()
|
public function subscriptionEnded()
|
||||||
{
|
{
|
||||||
$this->subscription->update([
|
$this->subscription->update([
|
||||||
'stripe_subscription_id' => null,
|
|
||||||
'stripe_plan_id' => null,
|
|
||||||
'stripe_cancel_at_period_end' => false,
|
'stripe_cancel_at_period_end' => false,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
'stripe_trial_already_ended' => false,
|
'stripe_trial_already_ended' => false,
|
||||||
|
63
app/Traits/EnvironmentVariableProtection.php
Normal file
63
app/Traits/EnvironmentVariableProtection.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
trait EnvironmentVariableProtection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check if an environment variable is protected from deletion
|
||||||
|
*
|
||||||
|
* @param string $key The environment variable key to check
|
||||||
|
* @return bool True if the variable is protected, false otherwise
|
||||||
|
*/
|
||||||
|
protected function isProtectedEnvironmentVariable(string $key): bool
|
||||||
|
{
|
||||||
|
return str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an environment variable is used in Docker Compose
|
||||||
|
*
|
||||||
|
* @param string $key The environment variable key to check
|
||||||
|
* @param string|null $dockerCompose The Docker Compose YAML content
|
||||||
|
* @return array [bool $isUsed, string $reason] Whether the variable is used and the reason if it is
|
||||||
|
*/
|
||||||
|
protected function isEnvironmentVariableUsedInDockerCompose(string $key, ?string $dockerCompose): array
|
||||||
|
{
|
||||||
|
if (empty($dockerCompose)) {
|
||||||
|
return [false, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dockerComposeData = Yaml::parse($dockerCompose);
|
||||||
|
$dockerEnvVars = data_get($dockerComposeData, 'services.*.environment');
|
||||||
|
|
||||||
|
foreach ($dockerEnvVars as $serviceEnvs) {
|
||||||
|
if (! is_array($serviceEnvs)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for direct variable usage
|
||||||
|
foreach ($serviceEnvs as $env => $value) {
|
||||||
|
if ($env === $key) {
|
||||||
|
return [true, "Environment variable '{$key}' is used directly in the Docker Compose file."];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for variable references in values
|
||||||
|
foreach ($serviceEnvs as $env => $value) {
|
||||||
|
if (is_string($value) && str_contains($value, '$'.$key)) {
|
||||||
|
return [true, "Environment variable '{$key}' is referenced in the Docker Compose file."];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If there's an error parsing the Docker Compose file, we'll assume it's not used
|
||||||
|
return [false, ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [false, ''];
|
||||||
|
}
|
||||||
|
}
|
@@ -24,6 +24,26 @@ function queue_application_deployment(Application $application, string $deployme
|
|||||||
if ($destination) {
|
if ($destination) {
|
||||||
$destination_id = $destination->id;
|
$destination_id = $destination->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there's already a deployment in progress or queued for this application and commit
|
||||||
|
$existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id)
|
||||||
|
->where('commit', $commit)
|
||||||
|
->whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existing_deployment) {
|
||||||
|
// If force_rebuild is true or rollback is true or no_questions_asked is true, we'll still create a new deployment
|
||||||
|
if (! $force_rebuild && ! $rollback && ! $no_questions_asked) {
|
||||||
|
// Return the existing deployment's details
|
||||||
|
return [
|
||||||
|
'status' => 'skipped',
|
||||||
|
'message' => 'Deployment already queued for this commit.',
|
||||||
|
'deployment_uuid' => $existing_deployment->deployment_uuid,
|
||||||
|
'existing_deployment' => $existing_deployment,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$deployment = ApplicationDeploymentQueue::create([
|
$deployment = ApplicationDeploymentQueue::create([
|
||||||
'application_id' => $application_id,
|
'application_id' => $application_id,
|
||||||
'application_name' => $application->name,
|
'application_name' => $application->name,
|
||||||
@@ -47,11 +67,17 @@ function queue_application_deployment(Application $application, string $deployme
|
|||||||
ApplicationDeploymentJob::dispatch(
|
ApplicationDeploymentJob::dispatch(
|
||||||
application_deployment_queue_id: $deployment->id,
|
application_deployment_queue_id: $deployment->id,
|
||||||
);
|
);
|
||||||
} elseif (next_queuable($server_id, $application_id)) {
|
} elseif (next_queuable($server_id, $application_id, $commit)) {
|
||||||
ApplicationDeploymentJob::dispatch(
|
ApplicationDeploymentJob::dispatch(
|
||||||
application_deployment_queue_id: $deployment->id,
|
application_deployment_queue_id: $deployment->id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 'queued',
|
||||||
|
'message' => 'Deployment queued.',
|
||||||
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||||
{
|
{
|
||||||
@@ -78,20 +104,35 @@ function queue_next_deployment(Application $application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function next_queuable(string $server_id, string $application_id): bool
|
function next_queuable(string $server_id, string $application_id, string $commit = 'HEAD'): bool
|
||||||
{
|
{
|
||||||
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at');
|
// Check if there's already a deployment in progress for this application and commit
|
||||||
$same_application_deployments = $deployments->where('application_id', $application_id);
|
$existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id)
|
||||||
$in_progress = $same_application_deployments->filter(function ($value, $key) {
|
->where('commit', $commit)
|
||||||
return $value->status === 'in_progress';
|
->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)
|
||||||
});
|
->first();
|
||||||
if ($in_progress->count() > 0) {
|
|
||||||
|
if ($existing_deployment) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there's any deployment in progress for this application
|
||||||
|
$in_progress = ApplicationDeploymentQueue::where('application_id', $application_id)
|
||||||
|
->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($in_progress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check server's concurrent build limit
|
||||||
$server = Server::find($server_id);
|
$server = Server::find($server_id);
|
||||||
$concurrent_builds = $server->settings->concurrent_builds;
|
$concurrent_builds = $server->settings->concurrent_builds;
|
||||||
|
$active_deployments = ApplicationDeploymentQueue::where('server_id', $server_id)
|
||||||
|
->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)
|
||||||
|
->count();
|
||||||
|
|
||||||
if ($deployments->count() > $concurrent_builds) {
|
if ($active_deployments >= $concurrent_builds) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -296,7 +296,8 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
|||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null)
|
|
||||||
|
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null, bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
|
||||||
{
|
{
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
if ($serviceLabels) {
|
if ($serviceLabels) {
|
||||||
@@ -304,6 +305,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
|||||||
} else {
|
} else {
|
||||||
$labels->push("caddy_ingress_network={$network}");
|
$labels->push("caddy_ingress_network={$network}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
|
||||||
|
|
||||||
foreach ($domains as $loop => $domain) {
|
foreach ($domains as $loop => $domain) {
|
||||||
$url = Url::fromString($domain);
|
$url = Url::fromString($domain);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
@@ -340,20 +344,30 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
|||||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||||
$labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}");
|
$labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}");
|
||||||
}
|
}
|
||||||
if (isDev()) {
|
if ($http_basic_auth_enabled) {
|
||||||
// $labels->push("caddy_{$loop}.tls=internal");
|
$http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
|
||||||
|
$labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$http_basic_auth_password}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $labels->sort();
|
return $labels->sort();
|
||||||
}
|
}
|
||||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both')
|
|
||||||
|
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null)
|
||||||
{
|
{
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
$labels->push('traefik.enable=true');
|
$labels->push('traefik.enable=true');
|
||||||
$labels->push('traefik.http.middlewares.gzip.compress=true');
|
$labels->push('traefik.http.middlewares.gzip.compress=true');
|
||||||
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
|
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
|
||||||
|
|
||||||
|
$http_basic_auth_enabled = $http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;
|
||||||
|
$http_basic_auth_label = "http-basic-auth-{$uuid}";
|
||||||
|
|
||||||
|
if ($http_basic_auth_enabled) {
|
||||||
|
$http_basic_auth_password = password_hash($http_basic_auth_password, PASSWORD_BCRYPT, ['cost' => 10]);
|
||||||
|
$labels->push("traefik.http.middlewares.{$http_basic_auth_label}.basicauth.users={$http_basic_auth_username}:{$http_basic_auth_password}");
|
||||||
|
}
|
||||||
|
|
||||||
$middlewares_from_labels = collect([]);
|
$middlewares_from_labels = collect([]);
|
||||||
|
|
||||||
if ($serviceLabels) {
|
if ($serviceLabels) {
|
||||||
@@ -511,6 +525,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
$labels = $labels->merge($redirect_to_www);
|
$labels = $labels->merge($redirect_to_www);
|
||||||
$middlewares->push($to_www_name);
|
$middlewares->push($to_www_name);
|
||||||
}
|
}
|
||||||
|
if ($http_basic_auth_enabled) {
|
||||||
|
$middlewares->push($http_basic_auth_label);
|
||||||
|
}
|
||||||
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
|
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
|
||||||
$middlewares->push($middleware_name);
|
$middlewares->push($middleware_name);
|
||||||
});
|
});
|
||||||
@@ -534,6 +551,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
$labels = $labels->merge($redirect_to_www);
|
$labels = $labels->merge($redirect_to_www);
|
||||||
$middlewares->push($to_www_name);
|
$middlewares->push($to_www_name);
|
||||||
}
|
}
|
||||||
|
if ($http_basic_auth_enabled) {
|
||||||
|
$middlewares->push($http_basic_auth_label);
|
||||||
|
}
|
||||||
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
|
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
|
||||||
$middlewares->push($middleware_name);
|
$middlewares->push($middleware_name);
|
||||||
});
|
});
|
||||||
@@ -562,6 +582,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
if ($pull_request_id !== 0) {
|
if ($pull_request_id !== 0) {
|
||||||
$appUuid = $appUuid.'-pr-'.$pull_request_id;
|
$appUuid = $appUuid.'-pr-'.$pull_request_id;
|
||||||
}
|
}
|
||||||
|
ray($application);
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
if ($pull_request_id === 0) {
|
if ($pull_request_id === 0) {
|
||||||
if ($application->fqdn) {
|
if ($application->fqdn) {
|
||||||
@@ -577,7 +598,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
redirect_direction: $application->redirect
|
redirect_direction: $application->redirect,
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case ProxyTypes::CADDY->value:
|
case ProxyTypes::CADDY->value:
|
||||||
@@ -589,7 +613,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
redirect_direction: $application->redirect
|
redirect_direction: $application->redirect,
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -601,7 +628,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
redirect_direction: $application->redirect
|
redirect_direction: $application->redirect,
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||||
network: $application->destination->network,
|
network: $application->destination->network,
|
||||||
@@ -611,7 +641,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
redirect_direction: $application->redirect
|
redirect_direction: $application->redirect,
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -631,7 +664,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
onlyPort: $onlyPort,
|
onlyPort: $onlyPort,
|
||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
case ProxyTypes::CADDY->value:
|
case ProxyTypes::CADDY->value:
|
||||||
@@ -642,7 +678,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
onlyPort: $onlyPort,
|
onlyPort: $onlyPort,
|
||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -653,7 +692,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
onlyPort: $onlyPort,
|
onlyPort: $onlyPort,
|
||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||||
network: $application->destination->network,
|
network: $application->destination->network,
|
||||||
@@ -662,7 +704,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
|||||||
onlyPort: $onlyPort,
|
onlyPort: $onlyPort,
|
||||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||||
is_gzip_enabled: $application->isGzipEnabled(),
|
is_gzip_enabled: $application->isGzipEnabled(),
|
||||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||||
|
http_basic_auth_enabled: $application->http_basic_auth_enabled,
|
||||||
|
http_basic_auth_username: $application->http_basic_auth_username,
|
||||||
|
http_basic_auth_password: $application->http_basic_auth_password,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,8 +727,10 @@ function isDatabaseImage(?string $image = null)
|
|||||||
$image = str($image)->append(':latest');
|
$image = str($image)->append(':latest');
|
||||||
}
|
}
|
||||||
$imageName = $image->before(':');
|
$imageName = $image->before(':');
|
||||||
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
|
foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) {
|
||||||
return true;
|
if (str($imageName)->contains($database_docker_image)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@@ -52,6 +52,9 @@ function generateGithubToken(GithubApp $source, string $type)
|
|||||||
|
|
||||||
if (! $response->successful()) {
|
if (! $response->successful()) {
|
||||||
$error = data_get($response->json(), 'message', 'no error message found');
|
$error = data_get($response->json(), 'message', 'no error message found');
|
||||||
|
if ($error === 'Not Found') {
|
||||||
|
$error = 'Repository not found. Is it moved or deleted?';
|
||||||
|
}
|
||||||
throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".$error);
|
throw new RuntimeException("Failed to get installation token for {$source->name} with error: ".$error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2987,7 +2987,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$predefinedPort = '8000';
|
$predefinedPort = '8000';
|
||||||
}
|
}
|
||||||
if ($isDatabase) {
|
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) {
|
if ($applicationFound) {
|
||||||
$savedService = $applicationFound;
|
$savedService = $applicationFound;
|
||||||
$savedService = ServiceDatabase::firstOrCreate([
|
$savedService = ServiceDatabase::firstOrCreate([
|
||||||
@@ -2999,178 +2999,174 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
} else {
|
} else {
|
||||||
$savedService = ServiceDatabase::firstOrCreate([
|
$savedService = ServiceDatabase::firstOrCreate([
|
||||||
'name' => $serviceName,
|
'name' => $serviceName,
|
||||||
'image' => $image,
|
|
||||||
'service_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$savedService = ServiceApplication::firstOrCreate([
|
$savedService = ServiceApplication::firstOrCreate([
|
||||||
'name' => $serviceName,
|
'name' => $serviceName,
|
||||||
'image' => $image,
|
|
||||||
'service_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$environment = collect(data_get($service, 'environment', []));
|
|
||||||
$buildArgs = collect(data_get($service, 'build.args', []));
|
|
||||||
$environment = $environment->merge($buildArgs);
|
|
||||||
|
|
||||||
// convert environment variables to one format
|
// Check if image changed
|
||||||
$environment = convertToKeyValueCollection($environment);
|
if ($savedService->image !== $image) {
|
||||||
|
$savedService->image = $image;
|
||||||
|
$savedService->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$environment = collect(data_get($service, 'environment', []));
|
||||||
|
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||||
|
$environment = $environment->merge($buildArgs);
|
||||||
|
|
||||||
// Add Coolify defined environments
|
// convert environment variables to one format
|
||||||
$allEnvironments = $resource->environment_variables()->get(['key', 'value']);
|
$environment = convertToKeyValueCollection($environment);
|
||||||
|
|
||||||
$allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
|
// Add Coolify defined environments
|
||||||
return [$item['key'] => $item['value']];
|
$allEnvironments = $resource->environment_variables()->get(['key', 'value']);
|
||||||
});
|
|
||||||
// filter and add magic environments
|
|
||||||
foreach ($environment as $key => $value) {
|
|
||||||
// Get all SERVICE_ variables from keys and values
|
|
||||||
$key = str($key);
|
|
||||||
$value = str($value);
|
|
||||||
|
|
||||||
$regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
|
$allEnvironments = $allEnvironments->mapWithKeys(function ($item) {
|
||||||
preg_match_all($regex, $value, $valueMatches);
|
return [$item['key'] => $item['value']];
|
||||||
if (count($valueMatches[1]) > 0) {
|
});
|
||||||
foreach ($valueMatches[1] as $match) {
|
// filter and add magic environments
|
||||||
$match = replaceVariables($match);
|
foreach ($environment as $key => $value) {
|
||||||
if ($match->startsWith('SERVICE_')) {
|
// Get all SERVICE_ variables from keys and values
|
||||||
if ($magicEnvironments->has($match->value())) {
|
$key = str($key);
|
||||||
continue;
|
$value = str($value);
|
||||||
}
|
|
||||||
$magicEnvironments->put($match->value(), '');
|
$regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/';
|
||||||
|
preg_match_all($regex, $value, $valueMatches);
|
||||||
|
if (count($valueMatches[1]) > 0) {
|
||||||
|
foreach ($valueMatches[1] as $match) {
|
||||||
|
$match = replaceVariables($match);
|
||||||
|
if ($match->startsWith('SERVICE_')) {
|
||||||
|
if ($magicEnvironments->has($match->value())) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
$magicEnvironments->put($match->value(), '');
|
||||||
}
|
|
||||||
|
|
||||||
// Get magic environments where we need to preset the FQDN
|
|
||||||
if ($key->startsWith('SERVICE_FQDN_')) {
|
|
||||||
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
|
||||||
if (substr_count(str($key)->value(), '_') === 3) {
|
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
|
|
||||||
$port = $key->afterLast('_')->value();
|
|
||||||
} else {
|
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
|
||||||
$port = null;
|
|
||||||
}
|
|
||||||
if ($isApplication) {
|
|
||||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
|
||||||
} elseif ($isService) {
|
|
||||||
if ($fqdnFor) {
|
|
||||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
|
||||||
} else {
|
|
||||||
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
|
|
||||||
$path = $value->value();
|
|
||||||
if ($path !== '/') {
|
|
||||||
$fqdn = "$fqdn$path";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$fqdnWithPort = $fqdn;
|
|
||||||
if ($port) {
|
|
||||||
$fqdnWithPort = "$fqdn:$port";
|
|
||||||
}
|
|
||||||
if ($isApplication && is_null($resource->fqdn)) {
|
|
||||||
data_forget($resource, 'environment_variables');
|
|
||||||
data_forget($resource, 'environment_variables_preview');
|
|
||||||
$resource->fqdn = $fqdnWithPort;
|
|
||||||
$resource->save();
|
|
||||||
} elseif ($isService && is_null($savedService->fqdn)) {
|
|
||||||
$savedService->fqdn = $fqdnWithPort;
|
|
||||||
$savedService->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (substr_count(str($key)->value(), '_') === 2) {
|
|
||||||
$resource->environment_variables()->firstOrCreate([
|
|
||||||
'key' => $key->value(),
|
|
||||||
'resourceable_type' => get_class($resource),
|
|
||||||
'resourceable_id' => $resource->id,
|
|
||||||
], [
|
|
||||||
'value' => $fqdn,
|
|
||||||
'is_build_time' => false,
|
|
||||||
'is_preview' => false,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (substr_count(str($key)->value(), '_') === 3) {
|
|
||||||
$newKey = str($key)->beforeLast('_');
|
|
||||||
$resource->environment_variables()->firstOrCreate([
|
|
||||||
'key' => $newKey->value(),
|
|
||||||
'resourceable_type' => get_class($resource),
|
|
||||||
'resourceable_id' => $resource->id,
|
|
||||||
], [
|
|
||||||
'value' => $fqdn,
|
|
||||||
'is_build_time' => false,
|
|
||||||
'is_preview' => false,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
// Get magic environments where we need to preset the FQDN
|
||||||
if ($magicEnvironments->count() > 0) {
|
if ($key->startsWith('SERVICE_FQDN_')) {
|
||||||
foreach ($magicEnvironments as $key => $value) {
|
// SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000
|
||||||
$key = str($key);
|
if (substr_count(str($key)->value(), '_') === 3) {
|
||||||
$value = replaceVariables($value);
|
$fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value();
|
||||||
$command = parseCommandFromMagicEnvVariable($key);
|
$port = $key->afterLast('_')->value();
|
||||||
$found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first();
|
} else {
|
||||||
if ($found) {
|
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||||
continue;
|
$port = null;
|
||||||
}
|
}
|
||||||
if ($command->value() === 'FQDN') {
|
if ($isApplication) {
|
||||||
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
$fqdn = generateFqdn($server, "$uuid");
|
||||||
if (str($fqdnFor)->contains('_')) {
|
} elseif ($isService) {
|
||||||
$fqdnFor = str($fqdnFor)->before('_');
|
if ($fqdnFor) {
|
||||||
}
|
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||||
if ($isApplication) {
|
|
||||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
|
||||||
} elseif ($isService) {
|
|
||||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
|
||||||
}
|
|
||||||
$resource->environment_variables()->firstOrCreate([
|
|
||||||
'key' => $key->value(),
|
|
||||||
'resourceable_type' => get_class($resource),
|
|
||||||
'resourceable_id' => $resource->id,
|
|
||||||
], [
|
|
||||||
'value' => $fqdn,
|
|
||||||
'is_build_time' => false,
|
|
||||||
'is_preview' => false,
|
|
||||||
]);
|
|
||||||
} elseif ($command->value() === 'URL') {
|
|
||||||
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
|
|
||||||
if (str($fqdnFor)->contains('_')) {
|
|
||||||
$fqdnFor = str($fqdnFor)->before('_');
|
|
||||||
}
|
|
||||||
if ($isApplication) {
|
|
||||||
$fqdn = generateFqdn($server, "{$resource->name}-$uuid");
|
|
||||||
} elseif ($isService) {
|
|
||||||
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
|
||||||
}
|
|
||||||
$fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
|
|
||||||
$resource->environment_variables()->firstOrCreate([
|
|
||||||
'key' => $key->value(),
|
|
||||||
'resourceable_type' => get_class($resource),
|
|
||||||
'resourceable_id' => $resource->id,
|
|
||||||
], [
|
|
||||||
'value' => $fqdn,
|
|
||||||
'is_build_time' => false,
|
|
||||||
'is_preview' => false,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
$value = generateEnvValue($command, $resource);
|
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
|
||||||
$resource->environment_variables()->firstOrCreate([
|
|
||||||
'key' => $key->value(),
|
|
||||||
'resourceable_type' => get_class($resource),
|
|
||||||
'resourceable_id' => $resource->id,
|
|
||||||
], [
|
|
||||||
'value' => $value,
|
|
||||||
'is_build_time' => false,
|
|
||||||
'is_preview' => false,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
|
||||||
|
$path = $value->value();
|
||||||
|
if ($path !== '/') {
|
||||||
|
$fqdn = "$fqdn$path";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fqdnWithPort = $fqdn;
|
||||||
|
if ($port) {
|
||||||
|
$fqdnWithPort = "$fqdn:$port";
|
||||||
|
}
|
||||||
|
if ($isApplication && is_null($resource->fqdn)) {
|
||||||
|
data_forget($resource, 'environment_variables');
|
||||||
|
data_forget($resource, 'environment_variables_preview');
|
||||||
|
$resource->fqdn = $fqdnWithPort;
|
||||||
|
$resource->save();
|
||||||
|
} elseif ($isService && is_null($savedService->fqdn)) {
|
||||||
|
$savedService->fqdn = $fqdnWithPort;
|
||||||
|
$savedService->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr_count(str($key)->value(), '_') === 2) {
|
||||||
|
$resource->environment_variables()->firstOrCreate([
|
||||||
|
'key' => $key->value(),
|
||||||
|
'resourceable_type' => get_class($resource),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
], [
|
||||||
|
'value' => $fqdn,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (substr_count(str($key)->value(), '_') === 3) {
|
||||||
|
$newKey = str($key)->beforeLast('_');
|
||||||
|
$resource->environment_variables()->firstOrCreate([
|
||||||
|
'key' => $newKey->value(),
|
||||||
|
'resourceable_type' => get_class($resource),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
], [
|
||||||
|
'value' => $fqdn,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
||||||
|
if ($magicEnvironments->count() > 0) {
|
||||||
|
foreach ($magicEnvironments as $key => $value) {
|
||||||
|
$key = str($key);
|
||||||
|
$value = replaceVariables($value);
|
||||||
|
$command = parseCommandFromMagicEnvVariable($key);
|
||||||
|
$found = $resource->environment_variables()->where('key', $key->value())->where('resourceable_type', get_class($resource))->where('resourceable_id', $resource->id)->first();
|
||||||
|
if ($found) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($command->value() === 'FQDN') {
|
||||||
|
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
|
||||||
|
if (str($fqdnFor)->contains('_')) {
|
||||||
|
$fqdnFor = str($fqdnFor)->before('_');
|
||||||
|
}
|
||||||
|
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||||
|
$resource->environment_variables()->firstOrCreate([
|
||||||
|
'key' => $key->value(),
|
||||||
|
'resourceable_type' => get_class($resource),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
], [
|
||||||
|
'value' => $fqdn,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
} elseif ($command->value() === 'URL') {
|
||||||
|
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value();
|
||||||
|
if (str($fqdnFor)->contains('_')) {
|
||||||
|
$fqdnFor = str($fqdnFor)->before('_');
|
||||||
|
}
|
||||||
|
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
|
||||||
|
$fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
$resource->environment_variables()->firstOrCreate([
|
||||||
|
'key' => $key->value(),
|
||||||
|
'resourceable_type' => get_class($resource),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
], [
|
||||||
|
'value' => $fqdn,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$value = generateEnvValue($command, $resource);
|
||||||
|
$resource->environment_variables()->firstOrCreate([
|
||||||
|
'key' => $key->value(),
|
||||||
|
'resourceable_type' => get_class($resource),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
], [
|
||||||
|
'value' => $value,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3201,6 +3197,15 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
$use_network_mode = data_get($service, 'network_mode') !== null;
|
$use_network_mode = data_get($service, 'network_mode') !== null;
|
||||||
$depends_on = collect(data_get($service, 'depends_on', []));
|
$depends_on = collect(data_get($service, 'depends_on', []));
|
||||||
$labels = collect(data_get($service, 'labels', []));
|
$labels = collect(data_get($service, 'labels', []));
|
||||||
|
if ($labels->count() > 0) {
|
||||||
|
if (isAssociativeArray($labels)) {
|
||||||
|
$newLabels = collect([]);
|
||||||
|
$labels->each(function ($value, $key) use ($newLabels) {
|
||||||
|
$newLabels->push("$key=$value");
|
||||||
|
});
|
||||||
|
$labels = $newLabels;
|
||||||
|
}
|
||||||
|
}
|
||||||
$environment = collect(data_get($service, 'environment', []));
|
$environment = collect(data_get($service, 'environment', []));
|
||||||
$ports = collect(data_get($service, 'ports', []));
|
$ports = collect(data_get($service, 'ports', []));
|
||||||
$buildArgs = collect(data_get($service, 'build.args', []));
|
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||||
|
@@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.405',
|
'version' => '4.0.0-beta.410',
|
||||||
'helper_version' => '1.0.8',
|
'helper_version' => '1.0.8',
|
||||||
'realtime_version' => '1.0.6',
|
'realtime_version' => '1.0.7',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
'autoupdate' => env('AUTOUPDATE'),
|
'autoupdate' => env('AUTOUPDATE'),
|
||||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||||
'registry_url' => env('REGISTRY_URL', 'ghcr.io'),
|
'registry_url' => env('REGISTRY_URL', 'ghcr.io'),
|
||||||
'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'),
|
'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'),
|
||||||
|
'realtime_image' => env('REALTIME_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-realtime'),
|
||||||
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->text('custom_network_aliases')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_network_aliases');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->longText('stripe_comment')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->longText('stripe_comment')->nullable(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->boolean('http_basic_auth_enabled')->default(false);
|
||||||
|
$table->string('http_basic_auth_username')->nullable(true)->default(null);
|
||||||
|
$table->string('http_basic_auth_password')->nullable(true)->default(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('http_basic_auth_enabled');
|
||||||
|
$table->dropColumn('http_basic_auth_username');
|
||||||
|
$table->dropColumn('http_basic_auth_password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
16
docker/coolify-realtime/package-lock.json
generated
16
docker/coolify-realtime/package-lock.json
generated
@@ -7,9 +7,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xterm/addon-fit": "0.10.0",
|
"@xterm/addon-fit": "0.10.0",
|
||||||
"@xterm/xterm": "5.5.0",
|
"@xterm/xterm": "5.5.0",
|
||||||
"axios": "1.7.9",
|
"axios": "1.8.4",
|
||||||
"cookie": "1.0.2",
|
"cookie": "1.0.2",
|
||||||
"dotenv": "16.4.7",
|
"dotenv": "16.5.0",
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.0.0",
|
||||||
"ws": "8.18.1"
|
"ws": "8.18.1"
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.7.9",
|
"version": "1.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
@@ -90,9 +90,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.7",
|
"version": "16.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
@@ -5,9 +5,9 @@
|
|||||||
"@xterm/addon-fit": "0.10.0",
|
"@xterm/addon-fit": "0.10.0",
|
||||||
"@xterm/xterm": "5.5.0",
|
"@xterm/xterm": "5.5.0",
|
||||||
"cookie": "1.0.2",
|
"cookie": "1.0.2",
|
||||||
"axios": "1.7.9",
|
"axios": "1.8.4",
|
||||||
"dotenv": "16.4.7",
|
"dotenv": "16.5.0",
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.0.0",
|
||||||
"ws": "8.18.1"
|
"ws": "8.18.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -89,9 +89,9 @@ RUN echo "alias ll='ls -al'" >> /etc/profile && \
|
|||||||
# Install Cloudflared based on architecture
|
# Install Cloudflared based on architecture
|
||||||
RUN mkdir -p /usr/local/bin && \
|
RUN mkdir -p /usr/local/bin && \
|
||||||
if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||||
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \
|
||||||
elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||||
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \
|
curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \
|
||||||
fi && \
|
fi && \
|
||||||
chmod +x /usr/local/bin/cloudflared
|
chmod +x /usr/local/bin/cloudflared
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Detect whether /dev/tty is available & functional
|
# Detect whether /dev/tty is available & functional
|
||||||
if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then
|
if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then
|
||||||
exec < /dev/tty
|
exec </dev/tty
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get list of stashed PHP files
|
# Get list of stashed PHP files
|
||||||
|
99
openapi.json
99
openapi.json
@@ -1798,6 +1798,18 @@
|
|||||||
"summary": "Update",
|
"summary": "Update",
|
||||||
"description": "Update application by UUID.",
|
"description": "Update application by UUID.",
|
||||||
"operationId": "update-application-by-uuid",
|
"operationId": "update-application-by-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "UUID of the application.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Application updated.",
|
"description": "Application updated.",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -4441,7 +4453,7 @@
|
|||||||
"Deployments"
|
"Deployments"
|
||||||
],
|
],
|
||||||
"summary": "Deploy",
|
"summary": "Deploy",
|
||||||
"description": "Deploy by tag or uuid. `Post` request also accepted.",
|
"description": "Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.",
|
||||||
"operationId": "deploy-by-tag-or-uuid",
|
"operationId": "deploy-by-tag-or-uuid",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -4529,6 +4541,40 @@
|
|||||||
"summary": "List application deployments",
|
"summary": "List application deployments",
|
||||||
"description": "List application deployments by using the app uuid",
|
"description": "List application deployments by using the app uuid",
|
||||||
"operationId": "list-deployments-by-app-uuid",
|
"operationId": "list-deployments-by-app-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "UUID of the application.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skip",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of records to skip.",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 0,
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of records to take.",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 10,
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "List application deployments by using the app uuid.",
|
"description": "List application deployments by using the app uuid.",
|
||||||
@@ -4921,6 +4967,18 @@
|
|||||||
"summary": "Update",
|
"summary": "Update",
|
||||||
"description": "Update Project.",
|
"description": "Update Project.",
|
||||||
"operationId": "update-project-by-uuid",
|
"operationId": "update-project-by-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "UUID of the project.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Project updated.",
|
"description": "Project updated.",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -5321,6 +5379,22 @@
|
|||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Private Key not found."
|
"description": "Private Key not found."
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Private Key is in use and cannot be deleted.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Private Key is in use and cannot be deleted."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
@@ -6222,6 +6296,18 @@
|
|||||||
"summary": "Update",
|
"summary": "Update",
|
||||||
"description": "Update service by UUID.",
|
"description": "Update service by UUID.",
|
||||||
"operationId": "update-service-by-uuid",
|
"operationId": "update-service-by-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "UUID of the service.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Service updated.",
|
"description": "Service updated.",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -7198,6 +7284,11 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"description": "Ports mappings."
|
"description": "Ports mappings."
|
||||||
},
|
},
|
||||||
|
"custom_network_aliases": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "Network aliases for Docker container."
|
||||||
|
},
|
||||||
"base_directory": {
|
"base_directory": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Base directory for all commands."
|
"description": "Base directory for all commands."
|
||||||
@@ -7638,6 +7729,12 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "private-key"
|
"format": "private-key"
|
||||||
},
|
},
|
||||||
|
"public_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fingerprint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"is_git_related": {
|
"is_git_related": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
72
openapi.yaml
72
openapi.yaml
@@ -1277,6 +1277,15 @@ paths:
|
|||||||
summary: Update
|
summary: Update
|
||||||
description: 'Update application by UUID.'
|
description: 'Update application by UUID.'
|
||||||
operationId: update-application-by-uuid
|
operationId: update-application-by-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the application.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
requestBody:
|
requestBody:
|
||||||
description: 'Application updated.'
|
description: 'Application updated.'
|
||||||
required: true
|
required: true
|
||||||
@@ -3085,7 +3094,7 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Deployments
|
- Deployments
|
||||||
summary: Deploy
|
summary: Deploy
|
||||||
description: 'Deploy by tag or uuid. `Post` request also accepted.'
|
description: 'Deploy by tag or uuid. `Post` request also accepted with `uuid` and `tag` json body.'
|
||||||
operationId: deploy-by-tag-or-uuid
|
operationId: deploy-by-tag-or-uuid
|
||||||
parameters:
|
parameters:
|
||||||
-
|
-
|
||||||
@@ -3135,6 +3144,33 @@ paths:
|
|||||||
summary: 'List application deployments'
|
summary: 'List application deployments'
|
||||||
description: 'List application deployments by using the app uuid'
|
description: 'List application deployments by using the app uuid'
|
||||||
operationId: list-deployments-by-app-uuid
|
operationId: list-deployments-by-app-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the application.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
-
|
||||||
|
name: skip
|
||||||
|
in: query
|
||||||
|
description: 'Number of records to skip.'
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 0
|
||||||
|
minimum: 0
|
||||||
|
-
|
||||||
|
name: take
|
||||||
|
in: query
|
||||||
|
description: 'Number of records to take.'
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 10
|
||||||
|
minimum: 1
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: 'List application deployments by using the app uuid.'
|
description: 'List application deployments by using the app uuid.'
|
||||||
@@ -3377,6 +3413,15 @@ paths:
|
|||||||
summary: Update
|
summary: Update
|
||||||
description: 'Update Project.'
|
description: 'Update Project.'
|
||||||
operationId: update-project-by-uuid
|
operationId: update-project-by-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the project.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
requestBody:
|
requestBody:
|
||||||
description: 'Project updated.'
|
description: 'Project updated.'
|
||||||
required: true
|
required: true
|
||||||
@@ -3630,6 +3675,14 @@ paths:
|
|||||||
$ref: '#/components/responses/400'
|
$ref: '#/components/responses/400'
|
||||||
'404':
|
'404':
|
||||||
description: 'Private Key not found.'
|
description: 'Private Key not found.'
|
||||||
|
'422':
|
||||||
|
description: 'Private Key is in use and cannot be deleted.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string, example: 'Private Key is in use and cannot be deleted.' }
|
||||||
|
type: object
|
||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
@@ -4145,6 +4198,15 @@ paths:
|
|||||||
summary: Update
|
summary: Update
|
||||||
description: 'Update service by UUID.'
|
description: 'Update service by UUID.'
|
||||||
operationId: update-service-by-uuid
|
operationId: update-service-by-uuid
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the service.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
requestBody:
|
requestBody:
|
||||||
description: 'Service updated.'
|
description: 'Service updated.'
|
||||||
required: true
|
required: true
|
||||||
@@ -4769,6 +4831,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
description: 'Ports mappings.'
|
description: 'Ports mappings.'
|
||||||
|
custom_network_aliases:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: 'Network aliases for Docker container.'
|
||||||
base_directory:
|
base_directory:
|
||||||
type: string
|
type: string
|
||||||
description: 'Base directory for all commands.'
|
description: 'Base directory for all commands.'
|
||||||
@@ -5093,6 +5159,10 @@ components:
|
|||||||
private_key:
|
private_key:
|
||||||
type: string
|
type: string
|
||||||
format: private-key
|
format: private-key
|
||||||
|
public_key:
|
||||||
|
type: string
|
||||||
|
fingerprint:
|
||||||
|
type: string
|
||||||
is_git_related:
|
is_git_related:
|
||||||
type: boolean
|
type: boolean
|
||||||
team_id:
|
team_id:
|
||||||
|
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.405"
|
"version": "4.0.0-beta.410"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.406"
|
"version": "4.0.0-beta.411"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.8"
|
"version": "1.0.8"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.6"
|
"version": "1.0.7"
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"version": "0.0.15"
|
"version": "0.0.15"
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@@ -22,7 +22,7 @@
|
|||||||
"pusher-js": "8.4.0",
|
"pusher-js": "8.4.0",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"tailwindcss": "3.4.17",
|
"tailwindcss": "3.4.17",
|
||||||
"vite": "^6.2.4",
|
"vite": "^6.2.6",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2994,9 +2994,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.2.4",
|
"version": "6.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||||
"integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
|
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
"pusher-js": "8.4.0",
|
"pusher-js": "8.4.0",
|
||||||
"tailwind-scrollbar": "^3.1.0",
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
"tailwindcss": "3.4.17",
|
"tailwindcss": "3.4.17",
|
||||||
"vite": "^6.2.4",
|
"vite": "^6.2.6",
|
||||||
"vue": "3.5.13"
|
"vue": "3.5.13"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@@ -8,13 +8,8 @@
|
|||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
<div class="subtitle">Your self-hosted infrastructure.</div>
|
<div class="subtitle">Your self-hosted infrastructure.</div>
|
||||||
@if (request()->query->get('success'))
|
@if (request()->query->get('success'))
|
||||||
<div class="items-center justify-center mb-10 font-bold rounded alert alert-success">
|
<div class=" mb-10 font-bold alert alert-success">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
Your subscription has been activated! Welcome onboard! It could take a few seconds before your
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
|
||||||
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
|
|
||||||
subscription is activated.<br> Please be patient.
|
subscription is activated.<br> Please be patient.
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
<div>
|
<div class="w-full px-2">
|
||||||
<x-modal-confirmation
|
<x-modal-confirmation buttonFullWidth title="Confirm Team Deletion?" buttonTitle="Delete Team" isErrorButton
|
||||||
title="Confirm Team Deletion?"
|
submitAction="delete" :actions="['The current Team will be permanently deleted.']" confirmationText="{{ $team }}"
|
||||||
buttonTitle="Delete Team"
|
|
||||||
isErrorButton
|
|
||||||
submitAction="delete"
|
|
||||||
:actions="['The current Team will be permanently deleted.']"
|
|
||||||
confirmationText="{{ $team }}"
|
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
|
confirmationLabel="Please confirm the execution of the actions by entering the Team Name below"
|
||||||
shortConfirmationLabel="Team Name"
|
shortConfirmationLabel="Team Name" step3ButtonText="Permanently Delete" />
|
||||||
step3ButtonText="Permanently Delete"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -342,6 +342,24 @@
|
|||||||
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
||||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
|
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
|
||||||
@endif
|
@endif
|
||||||
|
@if (!$application->destination->server->isSwarm())
|
||||||
|
<x-forms.input id="application.custom_network_aliases" label="Network Aliases"
|
||||||
|
helper="A comma separated list of custom network aliases you would like to add for container in Docker network.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>api.internal,api.local"
|
||||||
|
wire:model="application.custom_network_aliases" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="pt-8">HTTP Basic Authentication</h3>
|
||||||
|
<div x-data="{ enabled: {{ $application->http_basic_auth_enabled ? 'true' : 'false' }} }">
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox helper="This will add the proper proxy labels to the container."
|
||||||
|
label="Enable" id="application.http_basic_auth_enabled" x-model="enabled" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 py-2" x-show="enabled">
|
||||||
|
<x-forms.input id="application.http_basic_auth_username" label="Username" />
|
||||||
|
<x-forms.input id="application.http_basic_auth_password" type="password" label="Password" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($application->settings->is_container_label_readonly_enabled)
|
@if ($application->settings->is_container_label_readonly_enabled)
|
||||||
|
@@ -26,6 +26,11 @@
|
|||||||
<div class="pb-4">Code source of your application.</div>
|
<div class="pb-4">Code source of your application.</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
@if (!$privateKeyId)
|
||||||
|
<div>Currently connected source: <span
|
||||||
|
class="font-bold text-warning">{{ data_get($application, 'source.name', 'No source connected') }}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" />
|
<x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" />
|
||||||
<x-forms.input placeholder="main" id="gitBranch" label="Branch" />
|
<x-forms.input placeholder="main" id="gitBranch" label="Branch" />
|
||||||
@@ -34,6 +39,7 @@
|
|||||||
<x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" />
|
<x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($privateKeyId)
|
@if ($privateKeyId)
|
||||||
<h3 class="pt-4">Deploy Key</h3>
|
<h3 class="pt-4">Deploy Key</h3>
|
||||||
<div class="py-2 pt-4">Currently attached Private Key: <span
|
<div class="py-2 pt-4">Currently attached Private Key: <span
|
||||||
@@ -47,6 +53,38 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="pt-4">
|
||||||
|
<h3 class="pb-2">Change Git Source</h3>
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
@forelse ($sources as $source)
|
||||||
|
<div wire:key="{{ $source->name }}">
|
||||||
|
<x-modal-confirmation title="Change Git Source" :actions="['Change git source to ' . $source->name]" :buttonFullWidth="true"
|
||||||
|
:isHighlightedButton="$application->source_id === $source->id" :disabled="$application->source_id === $source->id"
|
||||||
|
submitAction="changeSource({{ $source->id }}, {{ $source->getMorphClass() }})"
|
||||||
|
:confirmWithText="true" confirmationText="Change Git Source"
|
||||||
|
confirmationLabel="Please confirm changing the git source by entering the text below"
|
||||||
|
shortConfirmationLabel="Confirmation Text" :confirmWithPassword="false">
|
||||||
|
<x-slot:customButton>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="box-title">
|
||||||
|
{{ $source->name }}
|
||||||
|
@if ($application->source_id === $source->id)
|
||||||
|
<span class="text-xs">(current)</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="box-description">
|
||||||
|
{{ $source->organization ?? 'Personal Account' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-slot:customButton>
|
||||||
|
</x-modal-confirmation>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div>No other sources found</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -20,7 +20,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-48 pb-2">
|
<div class="w-48 pb-2">
|
||||||
<x-forms.checkbox instantSave label="Backup Enabled" id="backupEnabled" />
|
<x-forms.checkbox instantSave label="Backup Enabled" id="backupEnabled" />
|
||||||
<x-forms.checkbox instantSave label="S3 Enabled" id="saveS3" />
|
@if ($s3s->count() > 0)
|
||||||
|
<x-forms.checkbox instantSave label="S3 Enabled" id="saveS3" />
|
||||||
|
@else
|
||||||
|
<x-forms.checkbox instantSave helper="No validated S3 storage available." label="S3 Enabled" id="saveS3"
|
||||||
|
disabled />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if ($backup->save_s3)
|
@if ($backup->save_s3)
|
||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
|
@@ -13,22 +13,41 @@
|
|||||||
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@if (version_compare($redis_version, '6.0', '>='))
|
@if ($database->started_at)
|
||||||
<x-forms.input label="Username" id="redis_username" required
|
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here,
|
||||||
helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable.
|
otherwise
|
||||||
|
automations won't work. <br>Changing them here will not change the values in the database.
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
@if (version_compare($redis_version, '6.0', '>='))
|
||||||
|
<x-forms.input label="Username" id="redis_username"
|
||||||
|
helper="You can only change this in the database." />
|
||||||
|
@endif
|
||||||
|
<x-forms.input label="Password" id="redis_password" type="password"
|
||||||
|
helper="You can only change this in the database." />
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="pt-2 dark:text-warning">You can only change the username and password in the database after
|
||||||
|
initial start.</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
@if (version_compare($redis_version, '6.0', '>='))
|
||||||
|
<x-forms.input label="Username" id="redis_username" required
|
||||||
|
helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable.
|
||||||
<br><br>
|
<br><br>
|
||||||
If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work.
|
If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work.
|
||||||
<br><br>
|
<br><br>
|
||||||
Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
||||||
:disabled="$this->isSharedVariable('REDIS_USERNAME')" />
|
:disabled="$this->isSharedVariable('REDIS_USERNAME')" />
|
||||||
@endif
|
@endif
|
||||||
<x-forms.input label="Password" id="redis_password" type="password" required
|
<x-forms.input label="Password" id="redis_password" type="password" required
|
||||||
helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable.
|
helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable.
|
||||||
<br><br>
|
<br><br>
|
||||||
If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work.
|
If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work.
|
||||||
<br><br>
|
<br><br>
|
||||||
Note: If the environment variable REDIS_PASSWORD is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
Note: If the environment variable REDIS_PASSWORD is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
||||||
:disabled="$this->isSharedVariable('REDIS_PASSWORD')" />
|
:disabled="$this->isSharedVariable('REDIS_PASSWORD')" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
@@ -129,7 +129,7 @@
|
|||||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||||
<div class="text-error">
|
<div class="text-error">
|
||||||
Unable to deploy. <a class="underline font-bold cursor-pointer"
|
Unable to deploy. <a class="underline font-bold cursor-pointer"
|
||||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'">
|
href="{{ route('project.service.environment-variables', $parameters) }}" wire:navigate>
|
||||||
Required environment variables missing.</a>
|
Required environment variables missing.</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -31,46 +31,48 @@
|
|||||||
@else
|
@else
|
||||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||||
@if ($is_multiline)
|
@if ($is_multiline)
|
||||||
<x-forms.input isMultiline="{{ $is_multiline }}" id="key" />
|
<x-forms.input :required="$is_redis_credential" isMultiline="{{ $is_multiline }}" id="key" />
|
||||||
<x-forms.textarea type="password" id="value" />
|
<x-forms.textarea :required="$is_redis_credential" type="password" id="value" />
|
||||||
@else
|
@else
|
||||||
<x-forms.input id="key" />
|
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" id="key" />
|
||||||
<x-forms.input type="password" id="value" />
|
<x-forms.input :required="$is_redis_credential" type="password" id="value" />
|
||||||
@endif
|
@endif
|
||||||
@if ($is_shared)
|
@if ($is_shared)
|
||||||
<x-forms.input disabled type="password" id="real_value" />
|
<x-forms.input :disabled="$is_redis_credential" :required="$is_redis_credential" disabled type="password" id="real_value" />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||||
@if ($type === 'service')
|
@if (!$is_redis_credential)
|
||||||
<x-forms.checkbox instantSave id="is_build_time"
|
@if ($type === 'service')
|
||||||
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
|
||||||
label="Build Variable?" />
|
|
||||||
<x-forms.checkbox instantSave id="is_literal"
|
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
|
||||||
label="Is Literal?" />
|
|
||||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
|
||||||
@else
|
|
||||||
@if ($is_shared)
|
|
||||||
<x-forms.checkbox instantSave id="is_build_time"
|
<x-forms.checkbox instantSave id="is_build_time"
|
||||||
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
||||||
label="Build Variable?" />
|
label="Build Variable?" />
|
||||||
<x-forms.checkbox instantSave id="is_literal"
|
<x-forms.checkbox instantSave id="is_literal"
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
label="Is Literal?" />
|
label="Is Literal?" />
|
||||||
|
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||||
@else
|
@else
|
||||||
@if ($isSharedVariable)
|
@if ($is_shared)
|
||||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
|
||||||
@else
|
|
||||||
<x-forms.checkbox instantSave id="is_build_time"
|
<x-forms.checkbox instantSave id="is_build_time"
|
||||||
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for docker file, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
||||||
label="Build Variable?" />
|
label="Build Variable?" />
|
||||||
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
<x-forms.checkbox instantSave id="is_literal"
|
||||||
@if ($is_multiline === false)
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
<x-forms.checkbox instantSave id="is_literal"
|
label="Is Literal?" />
|
||||||
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
@else
|
||||||
label="Is Literal?" />
|
@if ($isSharedVariable)
|
||||||
|
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||||
|
@else
|
||||||
|
<x-forms.checkbox instantSave id="is_build_time"
|
||||||
|
helper="If you are using Docker, remember to modify the file to be ready to receive the build time args. Ex.: for dockerfile, add `ARG name_of_the_variable`, or dockercompose add `- 'name_of_the_variable=${name_of_the_variable}'`"
|
||||||
|
label="Build Variable?" />
|
||||||
|
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
|
||||||
|
@if ($is_multiline === false)
|
||||||
|
<x-forms.checkbox instantSave id="is_literal"
|
||||||
|
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
|
||||||
|
label="Is Literal?" />
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
@@ -72,6 +72,7 @@
|
|||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
$wire.$on('checkProxyEvent', () => {
|
$wire.$on('checkProxyEvent', () => {
|
||||||
|
$wire.$dispatch('info', 'Checking proxy.');
|
||||||
$wire.$call('checkProxy');
|
$wire.$call('checkProxy');
|
||||||
});
|
});
|
||||||
$wire.$on('restartEvent', () => {
|
$wire.$on('restartEvent', () => {
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
wire:model="contents.{{ $fileName }}" rows="5" />
|
wire:model="contents.{{ $fileName }}" rows="5" />
|
||||||
@else
|
@else
|
||||||
<livewire:server.proxy.dynamic-configuration-navbar :server_id="$server->id"
|
<livewire:server.proxy.dynamic-configuration-navbar :server_id="$server->id"
|
||||||
:fileName="$fileName" :value="$value" :newFile="false"
|
:fileName="$fileName" :value="$value ?? ''" :newFile="false"
|
||||||
wire:key="{{ $fileName }}-{{ $loop->index }}" />
|
wire:key="{{ $fileName }}-{{ $loop->index }}" />
|
||||||
<x-forms.textarea disabled wire:model="contents.{{ $fileName }}"
|
<x-forms.textarea disabled wire:model="contents.{{ $fileName }}"
|
||||||
rows="10" />
|
rows="10" />
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
<div x-init="$wire.checkProxy()" class="flex gap-2">
|
<div @if (data_get($server, 'proxy.force_stop', false) === false) x-init="$wire.checkProxy()" @endif class="flex gap-2">
|
||||||
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
|
@if (data_get($server, 'proxy.force_stop', false) === false)
|
||||||
|
<x-forms.button wire:click='checkProxy(true)' :showLoadingIndicator="false">Refresh</x-forms.button>
|
||||||
|
@endif
|
||||||
@if (data_get($server, 'proxy.status') === 'running')
|
@if (data_get($server, 'proxy.status') === 'running')
|
||||||
<x-status.running status="Proxy Running" />
|
<x-status.running status="Proxy Running" />
|
||||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||||
|
@@ -277,12 +277,15 @@
|
|||||||
emails: 'read',
|
emails: 'read',
|
||||||
administration: 'read'
|
administration: 'read'
|
||||||
};
|
};
|
||||||
|
const default_events = ['push'];
|
||||||
if (preview_deployment_permissions) {
|
if (preview_deployment_permissions) {
|
||||||
default_permissions.pull_requests = 'write';
|
default_permissions.pull_requests = 'write';
|
||||||
|
default_events.push('pull_request');
|
||||||
}
|
}
|
||||||
if (administration) {
|
if (administration) {
|
||||||
default_permissions.administration = 'write';
|
default_permissions.administration = 'write';
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
name,
|
name,
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
@@ -297,7 +300,7 @@
|
|||||||
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
|
setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`,
|
||||||
setup_on_update: true,
|
setup_on_update: true,
|
||||||
default_permissions,
|
default_permissions,
|
||||||
default_events: ['pull_request', 'push']
|
default_events
|
||||||
};
|
};
|
||||||
const form = document.createElement('form');
|
const form = document.createElement('form');
|
||||||
form.setAttribute('method', 'post');
|
form.setAttribute('method', 'post');
|
||||||
|
@@ -3,37 +3,62 @@
|
|||||||
Subscribe | Coolify
|
Subscribe | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@if (auth()->user()->isAdminFromSession())
|
@if (auth()->user()->isAdminFromSession())
|
||||||
<div>
|
@if (request()->query->get('cancelled'))
|
||||||
<div class="flex gap-2">
|
<div class="mb-6 rounded alert-error">
|
||||||
<h1>Subscriptions</h1>
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||||
@if (subscriptionProvider() === 'stripe' && $alreadySubscribed)
|
viewBox="0 0 24 24">
|
||||||
<x-forms.button wire:click='stripeCustomerPortal'>Manage My Subscription</x-forms.button>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
@endif
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Something went wrong with your subscription. Please try again or contact
|
||||||
|
support.</span>
|
||||||
</div>
|
</div>
|
||||||
@if (request()->query->get('cancelled'))
|
@endif
|
||||||
<div class="mb-6 rounded alert-error">
|
<div class="flex gap-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
<h1>Subscriptions</h1>
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
|
||||||
<span>Something went wrong with your subscription. Please try again or contact
|
|
||||||
support.</span>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if (config('subscription.provider') === 'stripe')
|
|
||||||
<livewire:subscription.pricing-plans />
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
@if ($loading)
|
||||||
|
<div class="flex gap-2" wire:init="getStripeStatus">
|
||||||
|
Loading your subscription status...
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
@if ($isUnpaid)
|
||||||
|
<div class="mb-6 rounded alert-error">
|
||||||
|
<span>Your last payment was failed for Coolify Cloud.</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="mb-2">Open the following link, navigate to the button and pay your unpaid/past due
|
||||||
|
subscription.
|
||||||
|
</p>
|
||||||
|
<x-forms.button wire:click='stripeCustomerPortal'>Billing Portal</x-forms.button>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
@if (config('subscription.provider') === 'stripe')
|
||||||
|
<div @class([
|
||||||
|
'pb-4' => $isCancelled,
|
||||||
|
'pb-10' => !$isCancelled,
|
||||||
|
])>
|
||||||
|
@if ($isCancelled)
|
||||||
|
<div class="alert-error">
|
||||||
|
<span>It looks like your previous subscription has been cancelled, because you forgot to
|
||||||
|
pay
|
||||||
|
the bills.<br />Please subscribe again to continue using Coolify.</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<livewire:subscription.pricing-plans />
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col justify-center mx-10">
|
<div class="flex flex-col justify-center mx-10">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h1>Subscription</h1>
|
<h1>Subscription</h1>
|
||||||
</div>
|
</div>
|
||||||
<div>You are not an admin or have been removed from this team. If this does not make sense, please <span
|
<div>You are not an admin so you cannot manage your Team's subscription. If this does not make sense, please
|
||||||
class="underline cursor-pointer dark:text-white" wire:click="help">contact
|
<span class="underline cursor-pointer dark:text-white" wire:click="help">contact
|
||||||
us</span>.</div>
|
us</span>.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div x-data="{ selected: 'monthly' }" class="w-full pb-20 pt-10">
|
<div x-data="{ selected: 'monthly' }" class="w-full pb-20">
|
||||||
<div class="px-6 mx-auto lg:px-8">
|
<div class="px-6 mx-auto lg:px-8">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<fieldset
|
<fieldset
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flow-root mt-12">
|
<div class="flow-root mt-12">
|
||||||
<div
|
<div
|
||||||
class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-neutral-200 dark:divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-1 lg:divide-x lg:divide-y-0 xl:-mx-4">
|
class="grid grid-cols-1 -mt-16 divide-y divide-neutral-200 dark:divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-1 lg:divide-x lg:divide-y-0 xl:-mx-4">
|
||||||
<div class="pt-16 lg:px-8 lg:pt-0 xl:px-14">
|
<div class="pt-16 lg:px-8 lg:pt-0 xl:px-14">
|
||||||
<h3 id="tier-dynamic" class="text-4xl font-semibold leading-7 dark:text-white">Pay-as-you-go</h3>
|
<h3 id="tier-dynamic" class="text-4xl font-semibold leading-7 dark:text-white">Pay-as-you-go</h3>
|
||||||
<p class="mt-4 text-sm leading-6 dark:text-neutral-400">
|
<p class="mt-4 text-sm leading-6 dark:text-neutral-400">
|
||||||
@@ -72,14 +72,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
|
<div class="flex pt-4 h-14">
|
||||||
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-monthly')">
|
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
|
||||||
Subscribe
|
class="w-full" wire:click="subscribeStripe('dynamic-monthly')">
|
||||||
</x-forms.button>
|
Subscribe
|
||||||
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
|
</x-forms.button>
|
||||||
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-yearly')">
|
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
|
||||||
Subscribe
|
class="w-full" wire:click="subscribeStripe('dynamic-yearly')">
|
||||||
</x-forms.button>
|
Subscribe
|
||||||
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
|
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
|
||||||
<li class="flex">
|
<li class="flex">
|
||||||
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
|
@@ -16,8 +16,13 @@ use App\Models\Server;
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/health', [OtherController::class, 'healthcheck']);
|
Route::get('/health', [OtherController::class, 'healthcheck']);
|
||||||
Route::post('/feedback', [OtherController::class, 'feedback']);
|
Route::group([
|
||||||
|
'prefix' => 'v1',
|
||||||
|
], function () {
|
||||||
|
Route::get('/health', [OtherController::class, 'healthcheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::post('/feedback', [OtherController::class, 'feedback']);
|
||||||
Route::group([
|
Route::group([
|
||||||
'middleware' => ['auth:sanctum', 'api.ability:write'],
|
'middleware' => ['auth:sanctum', 'api.ability:write'],
|
||||||
'prefix' => 'v1',
|
'prefix' => 'v1',
|
||||||
@@ -117,7 +122,7 @@ Route::group([
|
|||||||
Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']);
|
Route::post('/services', [ServicesController::class, 'create_service'])->middleware(['api.ability:write']);
|
||||||
|
|
||||||
Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']);
|
Route::get('/services/{uuid}', [ServicesController::class, 'service_by_uuid'])->middleware(['api.ability:read']);
|
||||||
Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['ability:write']);
|
Route::patch('/services/{uuid}', [ServicesController::class, 'update_by_uuid'])->middleware(['api.ability:write']);
|
||||||
Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']);
|
Route::delete('/services/{uuid}', [ServicesController::class, 'delete_by_uuid'])->middleware(['api.ability:write']);
|
||||||
|
|
||||||
Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']);
|
Route::get('/services/{uuid}/envs', [ServicesController::class, 'envs'])->middleware(['api.ability:read']);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# documentation: https://docs.deno.com/deploy/kv/manual/
|
# documentation: https://docs.deno.com/deploy/kv/manual/
|
||||||
# slogan: The Denoland key-value database
|
# slogan: The Denoland key-value database
|
||||||
# tags: deno, kv, key-value, database
|
# tags: deno, kv, key-value, database
|
||||||
# logo: svgs/denoKV.svg
|
# logo: svgs/denokv.svg
|
||||||
# port: 4512
|
# port: 4512
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@@ -126,6 +126,7 @@ services:
|
|||||||
- S3_SECRET_KEY=${S3_SECRET_KEY:-}
|
- S3_SECRET_KEY=${S3_SECRET_KEY:-}
|
||||||
- S3_BUCKET=${S3_BUCKET:-evolution}
|
- S3_BUCKET=${S3_BUCKET:-evolution}
|
||||||
- S3_PORT=${S3_PORT:-443}
|
- S3_PORT=${S3_PORT:-443}
|
||||||
|
- S3_REGION=${S3_REGION:-us-east-1}
|
||||||
- S3_ENDPOINT=${S3_ENDPOINT:-files.site.com}
|
- S3_ENDPOINT=${S3_ENDPOINT:-files.site.com}
|
||||||
- S3_USE_SSL=${S3_USE_SSL:-true}
|
- S3_USE_SSL=${S3_USE_SSL:-true}
|
||||||
- 'AUTHENTICATION_API_KEY=${SERVICE_PASSWORD_AUTHENTICATIONAPIKEY}'
|
- 'AUTHENTICATION_API_KEY=${SERVICE_PASSWORD_AUTHENTICATIONAPIKEY}'
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
odoo:
|
odoo:
|
||||||
image: odoo:17
|
image: odoo:18
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_ODOO_8069
|
- SERVICE_FQDN_ODOO_8069
|
||||||
- HOST=postgresql
|
- HOST=postgresql
|
||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
- PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
- PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
volumes:
|
volumes:
|
||||||
- odoo-web-data:/var/lib/odoo
|
- odoo-web-data:/var/lib/odoo
|
||||||
|
- odoo-extra-addons:/mnt/extra-addons
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:8069"]
|
test: ["CMD", "curl", "-f", "http://127.0.0.1:8069"]
|
||||||
interval: 2s
|
interval: 2s
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
plausible:
|
plausible:
|
||||||
image: "ghcr.io/plausible/community-edition:v2.1.4"
|
image: "ghcr.io/plausible/community-edition:v3.0.1"
|
||||||
command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
|
command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_PLAUSIBLE
|
- SERVICE_FQDN_PLAUSIBLE
|
||||||
@@ -17,13 +17,19 @@ services:
|
|||||||
- TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP}
|
- TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP}
|
||||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||||
|
- MAILER_ADAPTER=${MAILER_ADAPTER:-Bamboo.LocalAdapter}
|
||||||
|
- MAILER_EMAIL=${MAILER_EMAIL}
|
||||||
|
- MAILER_NAME=${MAILER_NAME}
|
||||||
|
- SMTP_HOST_ADDR=${SMTP_HOST_ADDR}
|
||||||
|
- SMTP_HOST_PORT=${SMTP_HOST_PORT}
|
||||||
|
- SMTP_USER_NAME=${SMTP_USER_NAME}
|
||||||
|
- SMTP_USER_PWD=${SMTP_USER_PWD}
|
||||||
|
- SMTP_HOST_SSL_ENABLED=${SMTP_HOST_SSL_ENABLED}
|
||||||
depends_on:
|
depends_on:
|
||||||
plausible-db:
|
plausible-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
plausible-events-db:
|
plausible-events-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
mail:
|
|
||||||
condition: service_healthy
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
[
|
[
|
||||||
@@ -39,15 +45,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: 45s
|
start_period: 45s
|
||||||
|
|
||||||
mail:
|
|
||||||
image: bytemark/smtp
|
|
||||||
platform: linux/amd64
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/25' || exit 1"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 20
|
|
||||||
|
|
||||||
plausible-db:
|
plausible-db:
|
||||||
image: "postgres:16-alpine"
|
image: "postgres:16-alpine"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -63,7 +60,9 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
plausible-events-db:
|
plausible-events-db:
|
||||||
image: "clickhouse/clickhouse-server:24.3.3.102-alpine"
|
image: "clickhouse/clickhouse-server:24.12-alpine"
|
||||||
|
environment:
|
||||||
|
- CLICKHOUSE_SKIP_USER_SETUP=1
|
||||||
volumes:
|
volumes:
|
||||||
- plausible-events-data:/var/lib/clickhouse
|
- plausible-events-data:/var/lib/clickhouse
|
||||||
- type: bind
|
- type: bind
|
||||||
|
@@ -35,6 +35,8 @@ services:
|
|||||||
|
|
||||||
unsend:
|
unsend:
|
||||||
image: unsend/unsend:latest
|
image: unsend/unsend:latest
|
||||||
|
expose:
|
||||||
|
- 3000
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_FQDN_UNSEND_3000
|
- SERVICE_FQDN_UNSEND_3000
|
||||||
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${SERVICE_DB_POSTGRES:-unsend}
|
- DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${SERVICE_DB_POSTGRES:-unsend}
|
||||||
@@ -48,13 +50,14 @@ services:
|
|||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://redis:6379
|
||||||
- NEXT_PUBLIC_IS_CLOUD=${NEXT_PUBLIC_IS_CLOUD:-false}
|
- NEXT_PUBLIC_IS_CLOUD=${NEXT_PUBLIC_IS_CLOUD:-false}
|
||||||
- API_RATE_LIMIT=${API_RATE_LIMIT:-1}
|
- API_RATE_LIMIT=${API_RATE_LIMIT:-1}
|
||||||
|
- HOSTNAME=0.0.0.0
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:3000 || exit 1" ]
|
test: [ "CMD-SHELL", "wget -qO- http://unsend:3000 || exit 1" ]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.405"
|
"version": "4.0.0-beta.410"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.406"
|
"version": "4.0.0-beta.411"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.8"
|
"version": "1.0.8"
|
||||||
},
|
},
|
||||||
"realtime": {
|
"realtime": {
|
||||||
"version": "1.0.6"
|
"version": "1.0.7"
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"version": "0.0.15"
|
"version": "0.0.15"
|
||||||
|
Reference in New Issue
Block a user