mirror of
https://github.com/coollabsio/coolify.git
synced 2026-03-11 08:55:47 +00:00
Compare commits
40 commits
bbd547e64f
...
97897db82f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97897db82f | ||
|
|
cb759b2846 | ||
|
|
d8419fad93 | ||
|
|
279322d50f | ||
|
|
f39a1da7be | ||
|
|
448e922e6c | ||
|
|
78e584a136 | ||
|
|
912e5f6db2 | ||
|
|
f8de374f77 | ||
|
|
2986d7604e | ||
|
|
b36d67288b | ||
|
|
021605dbf0 | ||
|
|
ec14b55f0a | ||
|
|
2310ad5f7f | ||
|
|
6cacd2f0ff | ||
|
|
46923f7e77 | ||
|
|
620da191b1 | ||
|
|
d71d91d63e | ||
|
|
1f3fca5f71 | ||
|
|
76a6960f44 | ||
|
|
f68d60a373 | ||
|
|
b7b0dfeddd | ||
|
|
133241bac1 | ||
|
|
61a54afe2b | ||
|
|
58acdccfc9 | ||
|
|
bf51ed905f | ||
|
|
c30d94f089 | ||
|
|
cb0f5cc812 | ||
|
|
ffb408f214 | ||
|
|
76d3709163 | ||
|
|
c1951726c0 | ||
|
|
7c4f8f37a3 | ||
|
|
2cd1082fbd | ||
|
|
17f5259f32 | ||
|
|
331493a0b0 | ||
|
|
346b2b8bd8 | ||
|
|
4045c7267e | ||
|
|
c180947d6b | ||
|
|
32e1fd97ae | ||
|
|
960ceddf15 |
35 changed files with 953 additions and 279 deletions
144
CHANGELOG.md
144
CHANGELOG.md
|
|
@ -1190,7 +1190,41 @@ All notable changes to this project will be documented in this file.
|
|||
- *(service)* Update autobase to version 2.5 (#7923)
|
||||
- *(service)* Add chibisafe template (#5808)
|
||||
- *(ui)* Improve sidebar menu items styling (#7928)
|
||||
- *(service)* Improve open-archiver
|
||||
- *(template)* Add open archiver template (#6593)
|
||||
- *(service)* Add linkding template (#6651)
|
||||
- *(service)* Add glip template (#7937)
|
||||
- *(templates)* Add Sessy docker compose template (#7951)
|
||||
- *(api)* Add update urls support to services api
|
||||
- *(api)* Improve service urls update
|
||||
- *(api)* Add url update support to services api (#7929)
|
||||
- *(api)* Improve docker_compose_domains
|
||||
- *(api)* Add more allowed fields
|
||||
- *(notifications)* Add mattermost notifications (#7963)
|
||||
- *(templates)* Add ElectricSQL docker compose template
|
||||
- *(service)* Add back soketi-app-manager
|
||||
- *(service)* Upgrade checkmate to v3 (#7995)
|
||||
- *(service)* Update pterodactyl version (#7981)
|
||||
- *(service)* Add langflow template (#8006)
|
||||
- *(service)* Upgrade listmonk to v6
|
||||
- *(service)* Add alexandrie template (#8021)
|
||||
- *(service)* Upgrade formbricks to v4 (#8022)
|
||||
- *(service)* Add goatcounter template (#8029)
|
||||
- *(installer)* Add tencentos as a supported os
|
||||
- *(installer)* Update nightly install script
|
||||
- Update pr template to remove unnecessary quote blocks
|
||||
- *(service)* Add satisfactory game server (#8056)
|
||||
- *(service)* Disable mautic (#8088)
|
||||
- *(service)* Add bento-pdf (#8095)
|
||||
- *(ui)* Add official postgres 18 support
|
||||
- *(database)* Add official postgres 18 support
|
||||
- *(ui)* Use 2 column layout
|
||||
- *(database)* Add official postgres 18 and pgvector 18 support (#8143)
|
||||
- *(ui)* Improve global search with uuid and pr support (#7901)
|
||||
- *(openclaw)* Add Openclaw service with environment variables and health checks
|
||||
- *(service)* Disable maybe
|
||||
- *(service)* Disable maybe (#8167)
|
||||
- *(service)* Add sure
|
||||
- *(service)* Add sure (#8157)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
|
|
@ -3773,6 +3807,7 @@ All notable changes to this project will be documented in this file.
|
|||
- *(scheduling)* Change redis cleanup command frequency from hourly to weekly for better resource management
|
||||
- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.5 and 4.0.0-beta.420.6
|
||||
- *(database)* Ensure internal port defaults correctly for unsupported database types in StartDatabaseProxy
|
||||
- *(git)* Tracking issue due to case sensitivity
|
||||
- *(versions)* Update coolify version numbers in versions.json and constants.php to 4.0.0-beta.420.6 and 4.0.0-beta.420.7
|
||||
- *(scheduling)* Remove unnecessary padding from scheduled task form layout for improved UI consistency
|
||||
- *(horizon)* Update queue configuration to use environment variable for dynamic queue management
|
||||
|
|
@ -3798,7 +3833,6 @@ All notable changes to this project will be documented in this file.
|
|||
- *(application)* Add option to suppress toast notifications when loading compose file
|
||||
- *(git)* Tracking issue due to case sensitivity
|
||||
- *(git)* Tracking issue due to case sensitivity
|
||||
- *(git)* Tracking issue due to case sensitivity
|
||||
- *(ui)* Delete button width on small screens (#6308)
|
||||
- *(service)* Matrix entrypoint
|
||||
- *(ui)* Add flex-wrap to prevent overflow on small screens (#6307)
|
||||
|
|
@ -4422,6 +4456,67 @@ All notable changes to this project will be documented in this file.
|
|||
- *(api)* Deprecate applications compose endpoint
|
||||
- *(api)* Applications post and patch endpoints
|
||||
- *(api)* Applications create and patch endpoints (#7917)
|
||||
- *(service)* Sftpgo port
|
||||
- *(env)* Only cat .env file in dev
|
||||
- *(api)* Encoding checks (#7944)
|
||||
- *(env)* Only show nixpacks plan variables section in dev
|
||||
- Switch custom labels check to UTF-8
|
||||
- *(api)* One click service name and description cannot be set during creation
|
||||
- *(ui)* Improve volume mount warning for compose applications (#7947)
|
||||
- *(api)* Show an error if the same 2 urls are provided
|
||||
- *(preview)* Docker compose preview URLs (#7959)
|
||||
- *(api)* Check domain conflicts within the request
|
||||
- *(api)* Include docker_compose_domains in domain conflict check
|
||||
- *(api)* Is_static and docker network missing
|
||||
- *(api)* If domains field is empty clear the fqdn column
|
||||
- *(api)* Application endpoint issues part 2 (#7948)
|
||||
- Optimize queries and caching for projects and environments
|
||||
- *(perf)* Eliminate N+1 queries from InstanceSettings and Server lookups (#7966)
|
||||
- Update version numbers to 4.0.0-beta.462 and 4.0.0-beta.463
|
||||
- *(service)* Update seaweedfs logo (#7971)
|
||||
- *(service)* Soju svg
|
||||
- *(service)* Autobase database is not persisted correctly (#7978)
|
||||
- *(ui)* Make tooltips a bit wider
|
||||
- *(ui)* Modal issues
|
||||
- *(validation)* Add @, / and & support to names and descriptions
|
||||
- *(backup)* Postgres restore arithmetic syntax error (#7997)
|
||||
- *(service)* Users unable to create their first ente account without SMTP (#7986)
|
||||
- *(ui)* Horizontal overflow on application and service headings (#7970)
|
||||
- *(service)* Supabase studio settings redirect loop (#7828)
|
||||
- *(env)* Skip escaping for valid JSON in environment variables (#6160)
|
||||
- *(service)* Disable kong response buffering and increase timeouts (#7864)
|
||||
- *(service)* Rocketchat fails to start due to database version incompatibility (#7999)
|
||||
- *(service)* N8n v2 with worker timeout error
|
||||
- *(service)* Elasticsearch-with-kibana not generating account token
|
||||
- *(service)* Elasticsearch-with-kibana not generating account token (#8067)
|
||||
- *(service)* Kimai fails to start (#8027)
|
||||
- *(service)* Reactive-resume template (#8048)
|
||||
- *(api)* Infinite loop with github app with many repos (#8052)
|
||||
- *(env)* Skip escaping for valid JSON in environment variables (#8080)
|
||||
- *(docker)* Update PostgreSQL version to 16 in Dockerfile
|
||||
- *(validation)* Enforce url validation for instance domain (#8078)
|
||||
- *(service)* Bluesky pds invite code doesn't generate (#8081)
|
||||
- *(service)* Bugsink login fails due to cors (#8083)
|
||||
- *(service)* Strapi doesn't start (#8084)
|
||||
- *(service)* Activepieces postgres 18 volume mount (#8098)
|
||||
- *(service)* Forgejo login failure (#8145)
|
||||
- *(database)* Pgvector 18 version is not parsed properly
|
||||
- *(labels)* Make sure name is slugified
|
||||
- *(parser)* Replace dashes and dots in auto generated envs
|
||||
- Stop database proxy when is_public changes to false (#8138)
|
||||
- *(docs)* Update documentation link for Openclaw service
|
||||
- *(api-docs)* Use proper schema references for environment variable endpoints (#8239)
|
||||
- *(ui)* Fix datalist border color and add repository selection watcher (#8240)
|
||||
- *(server)* Improve IP uniqueness validation with team-specific error messages
|
||||
- *(jobs)* Initialize status variable in checkHetznerStatus (#8359)
|
||||
- *(jobs)* Handle queue timeouts gracefully in Horizon (#8360)
|
||||
- *(push-server-job)* Skip containers with empty service subId (#8361)
|
||||
- *(database)* Disable proxy on port allocation failure (#8362)
|
||||
- *(sentry)* Use withScope for SSH retry event tracking (#8363)
|
||||
- *(api)* Add a newline to openapi.json
|
||||
- *(server)* Improve IP uniqueness validation with team-specific error messages
|
||||
- *(service)* Glitchtip webdashboard doesn't load
|
||||
- *(service)* Glitchtip webdashboard doesn't load (#8249)
|
||||
|
||||
### 💼 Other
|
||||
|
||||
|
|
@ -4886,6 +4981,7 @@ All notable changes to this project will be documented in this file.
|
|||
- CVE-2025-55182 React2shell infected supabase/studio:2025.06.02-sha-8f2993d
|
||||
- Bump superset to 6.0.0
|
||||
- Trim whitespace from domain input in instance settings (#7837)
|
||||
- Upgrade postgres client to fix build error
|
||||
|
||||
### 🚜 Refactor
|
||||
|
||||
|
|
@ -5510,6 +5606,10 @@ All notable changes to this project will be documented in this file.
|
|||
- Move all env sorting to one place
|
||||
- *(api)* Make docker_compose_raw description more clear
|
||||
- *(api)* Update application create endpoints docs
|
||||
- *(api)* Application urls validation
|
||||
- *(services)* Improve some service slogans
|
||||
- *(ssh-retry)* Remove Sentry tracking from retry logic
|
||||
- *(ssh-retry)* Remove Sentry tracking from retry logic
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
|
|
@ -5616,7 +5716,6 @@ All notable changes to this project will be documented in this file.
|
|||
- Update changelog
|
||||
- *(tests)* Update testing guidelines for unit and feature tests
|
||||
- *(sync)* Create AI Instructions Synchronization Guide and update CLAUDE.md references
|
||||
- Update changelog
|
||||
- *(database-patterns)* Add critical note on mass assignment protection for new columns
|
||||
- Clarify cloud-init script compatibility
|
||||
- Update changelog
|
||||
|
|
@ -5647,6 +5746,15 @@ All notable changes to this project will be documented in this file.
|
|||
- Update application architecture and database patterns for request-level caching best practices
|
||||
- Remove git worktree symlink instructions from CLAUDE.md
|
||||
- Remove git worktree symlink instructions from CLAUDE.md (#7908)
|
||||
- Add transcript lol link and logo to readme (#7331)
|
||||
- *(api)* Change domains to urls
|
||||
- *(api)* Improve domains API docs
|
||||
- Update changelog
|
||||
- Update changelog
|
||||
- *(api)* Improve app endpoint deprecation description
|
||||
- Add Coolify design system reference
|
||||
- Add Coolify design system reference (#8237)
|
||||
- Update changelog
|
||||
- Update changelog
|
||||
|
||||
### ⚡ Performance
|
||||
|
|
@ -5686,6 +5794,9 @@ All notable changes to this project will be documented in this file.
|
|||
- Add tests for shared environment variable spacing and resolution
|
||||
- Add comprehensive preview deployment port and path tests
|
||||
- Add comprehensive preview deployment port and path tests (#7677)
|
||||
- Add Pest browser testing with SQLite :memory: schema
|
||||
- Add dashboard test and improve browser test coverage
|
||||
- Migrate to SQLite :memory: and add Pest browser testing (#8364)
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
|
|
@ -6293,10 +6404,10 @@ All notable changes to this project will be documented in this file.
|
|||
- *(versions)* Update Coolify versions to 4.0.0-beta.420.2 and 4.0.0-beta.420.3 in multiple files
|
||||
- *(versions)* Bump coolify and nightly versions to 4.0.0-beta.420.3 and 4.0.0-beta.420.4 respectively
|
||||
- *(versions)* Update coolify and nightly versions to 4.0.0-beta.420.4 and 4.0.0-beta.420.5 respectively
|
||||
- *(service)* Update Nitropage template (#6181)
|
||||
- *(versions)* Update all version
|
||||
- *(bump)* Update composer deps
|
||||
- *(version)* Bump Coolify version to 4.0.0-beta.420.6
|
||||
- *(service)* Update Nitropage template (#6181)
|
||||
- *(versions)* Update all version
|
||||
- *(service)* Improve matrix service
|
||||
- *(service)* Format runner service
|
||||
- *(service)* Improve sequin
|
||||
|
|
@ -6399,6 +6510,29 @@ All notable changes to this project will be documented in this file.
|
|||
- *(services)* Upgrade service template json files
|
||||
- *(api)* Update openapi json and yaml
|
||||
- *(api)* Regenerate openapi docs
|
||||
- Prepare for PR
|
||||
- *(api)* Improve current request error message
|
||||
- *(api)* Improve current request error message
|
||||
- *(api)* Update openapi files
|
||||
- *(service)* Update service templates json
|
||||
- *(services)* Update service template json files
|
||||
- *(service)* Use major version for openpanel (#8053)
|
||||
- Prepare for PR
|
||||
- *(services)* Update service template json files
|
||||
- Bump coolify version
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
- Prepare for PR
|
||||
|
||||
### ◀️ Revert
|
||||
|
||||
|
|
|
|||
|
|
@ -207,6 +207,9 @@ class StartKeydb
|
|||
if ($this->database->enable_ssl) {
|
||||
$this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt";
|
||||
}
|
||||
if (! is_null($this->database->keydb_conf) && ! empty($this->database->keydb_conf)) {
|
||||
$this->commands[] = "chown 999:999 $this->configuration_dir/keydb.conf";
|
||||
}
|
||||
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
|
||||
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
|
|
|
|||
|
|
@ -204,6 +204,9 @@ class StartRedis
|
|||
if ($this->database->enable_ssl) {
|
||||
$this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt";
|
||||
}
|
||||
if (! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf)) {
|
||||
$this->commands[] = "chown 999:999 $this->configuration_dir/redis.conf";
|
||||
}
|
||||
$this->commands[] = "docker stop -t 10 $container_name 2>/dev/null || true";
|
||||
$this->commands[] = "docker rm -f $container_name 2>/dev/null || true";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class CheckForcePasswordReset
|
|||
}
|
||||
$force_password_reset = auth()->user()->force_password_reset;
|
||||
if ($force_password_reset) {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'two-factor-challenge' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
private bool $dockerBuildkitSupported = false;
|
||||
|
||||
private bool $dockerSecretsSupported = false;
|
||||
|
||||
private bool $skip_build = false;
|
||||
|
||||
private Collection|string $build_secrets;
|
||||
|
|
@ -381,13 +383,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
private function detectBuildKitCapabilities(): void
|
||||
{
|
||||
// If build secrets are not enabled, skip detection and use traditional args
|
||||
if (! $this->application->settings->use_build_secrets) {
|
||||
$this->dockerBuildkitSupported = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$serverToCheck = $this->use_build_server ? $this->build_server : $this->server;
|
||||
$serverName = $this->use_build_server ? "build server ({$serverToCheck->name})" : "deployment server ({$serverToCheck->name})";
|
||||
|
||||
|
|
@ -403,53 +398,55 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
if ($majorVersion < 18 || ($majorVersion == 18 && $minorVersion < 9)) {
|
||||
$this->dockerBuildkitSupported = false;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+). Build secrets feature disabled.");
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit (requires 18.09+).");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$buildkitEnabled = instant_remote_process(
|
||||
// Check buildx availability (always installed by Coolify on Docker 24.0+)
|
||||
$buildxAvailable = instant_remote_process(
|
||||
["docker buildx version >/dev/null 2>&1 && echo 'available' || echo 'not-available'"],
|
||||
$serverToCheck
|
||||
);
|
||||
|
||||
if (trim($buildkitEnabled) !== 'available') {
|
||||
if (trim($buildxAvailable) === 'available') {
|
||||
$this->dockerBuildkitSupported = true;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
||||
} else {
|
||||
// Fallback: test DOCKER_BUILDKIT=1 support via --progress flag
|
||||
$buildkitTest = instant_remote_process(
|
||||
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
|
||||
["DOCKER_BUILDKIT=1 docker build --help 2>&1 | grep -q '\\-\\-progress' && echo 'supported' || echo 'not-supported'"],
|
||||
$serverToCheck
|
||||
);
|
||||
|
||||
if (trim($buildkitTest) === 'supported') {
|
||||
$this->dockerBuildkitSupported = true;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit secrets support detected on {$serverName}.");
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit support detected on {$serverName}.");
|
||||
} else {
|
||||
$this->dockerBuildkitSupported = false;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not have BuildKit secrets support.");
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} on {$serverName} does not support BuildKit. Build output progress will be limited.");
|
||||
}
|
||||
} else {
|
||||
// Buildx is available, which means BuildKit is available
|
||||
// Now specifically test for secrets support
|
||||
}
|
||||
|
||||
// If build secrets are enabled and BuildKit is available, verify --secret flag support
|
||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported) {
|
||||
$secretsTest = instant_remote_process(
|
||||
["docker build --help 2>&1 | grep -q 'secret' && echo 'supported' || echo 'not-supported'"],
|
||||
$serverToCheck
|
||||
);
|
||||
|
||||
if (trim($secretsTest) === 'supported') {
|
||||
$this->dockerBuildkitSupported = true;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with BuildKit and Buildx detected on {$serverName}.");
|
||||
$this->dockerSecretsSupported = true;
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets are enabled and will be used for enhanced security.');
|
||||
} else {
|
||||
$this->dockerBuildkitSupported = false;
|
||||
$this->application_deployment_queue->addLogEntry("Docker {$dockerVersion} with Buildx on {$serverName}, but secrets not supported.");
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but not supported. Using traditional build arguments.');
|
||||
$this->dockerSecretsSupported = false;
|
||||
$this->application_deployment_queue->addLogEntry("Docker on {$serverName} does not support build secrets. Using traditional build arguments.");
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->dockerBuildkitSupported = false;
|
||||
$this->dockerSecretsSupported = false;
|
||||
$this->application_deployment_queue->addLogEntry("Could not detect BuildKit capabilities on {$serverName}: {$e->getMessage()}");
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets feature is enabled but detection failed. Using traditional build arguments.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -632,7 +629,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
|
||||
// For raw compose, we cannot automatically add secrets configuration
|
||||
// User must define it manually in their docker-compose file
|
||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||
$this->application_deployment_queue->addLogEntry('Build secrets are configured. Ensure your docker-compose file includes build.secrets configuration for services that need them.');
|
||||
}
|
||||
} else {
|
||||
|
|
@ -653,7 +650,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
}
|
||||
|
||||
// Add build secrets to compose file if enabled and BuildKit is supported
|
||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||
$composeFile = $this->add_build_secrets_to_compose($composeFile);
|
||||
}
|
||||
|
||||
|
|
@ -2817,7 +2814,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||
}
|
||||
}
|
||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
if ($this->dockerBuildkitSupported) {
|
||||
$build_command = "DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
} else {
|
||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile -t {$this->production_image_name} {$this->workdir}";
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
|
|
@ -2857,21 +2858,19 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
private function build_image()
|
||||
{
|
||||
// Add Coolify related variables to the build args/secrets
|
||||
if ($this->dockerBuildkitSupported) {
|
||||
// Coolify variables are already included in the secrets from generate_build_env_variables
|
||||
// build_secrets is already a string at this point
|
||||
} else {
|
||||
if (! $this->dockerBuildkitSupported) {
|
||||
// Traditional build args approach - generate COOLIFY_ variables locally
|
||||
// Generate COOLIFY_ variables locally for build args
|
||||
$coolify_envs = $this->generate_coolify_env_variables(forBuildTime: true);
|
||||
$coolify_envs->each(function ($value, $key) {
|
||||
$this->build_args->push("--build-arg '{$key}'");
|
||||
});
|
||||
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
|
||||
? $this->build_args->implode(' ')
|
||||
: (string) $this->build_args;
|
||||
}
|
||||
|
||||
// Always convert build_args Collection to string for command interpolation
|
||||
$this->build_args = $this->build_args instanceof \Illuminate\Support\Collection
|
||||
? $this->build_args->implode(' ')
|
||||
: (string) $this->build_args;
|
||||
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
if ($this->disableBuildCache) {
|
||||
$this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.');
|
||||
|
|
@ -2899,7 +2898,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the nixpacks Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
|
|
@ -2907,9 +2906,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
ray($build_command);
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
|
|
@ -2919,18 +2917,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the nixpacks Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->workdir}");
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->build_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2952,7 +2948,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
||||
} else {
|
||||
// Dockerfile buildpack
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
|
|
@ -2963,19 +2959,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||
}
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} --progress plain -t $this->build_image_name {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
// Traditional build with args
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} --network {$this->destination->network} -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t $this->build_image_name {$this->workdir}");
|
||||
}
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
|
@ -3010,7 +3004,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||
}
|
||||
}
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
if ($this->dockerBuildkitSupported) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
|
|
@ -3035,7 +3033,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
} else {
|
||||
// Pure Dockerfile based deployment
|
||||
if ($this->application->dockerfile) {
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
|
|
@ -3044,12 +3042,19 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
} else {
|
||||
$build_command = "DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
}
|
||||
} else {
|
||||
// Traditional build with args
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
// Traditional build with args (no --progress for legacy builder compatibility)
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||
}
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
|
@ -3079,18 +3084,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the nixpacks Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
|
|
@ -3100,18 +3103,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/.nixpacks/Dockerfile"),
|
||||
'hidden' => true,
|
||||
]);
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the nixpacks Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}/.nixpacks/Dockerfile");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
|
@ -3132,7 +3133,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm '.self::NIXPACKS_PLAN_PATH), 'hidden' => true]);
|
||||
} else {
|
||||
// Dockerfile buildpack
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
// Modify the Dockerfile to use build secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
// Use BuildKit with secrets
|
||||
|
|
@ -3144,19 +3145,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
}
|
||||
} elseif ($this->dockerBuildkitSupported) {
|
||||
// BuildKit without secrets
|
||||
$this->modify_dockerfile_for_secrets("{$this->workdir}{$this->dockerfile_location}");
|
||||
$secrets_flags = $this->build_secrets ? " {$this->build_secrets}" : '';
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location}{$secrets_flags} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("DOCKER_BUILDKIT=1 docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} --progress plain -t {$this->production_image_name} {$this->build_args} {$this->workdir}");
|
||||
}
|
||||
} else {
|
||||
// Traditional build with args
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build --no-cache {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||
} else {
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}");
|
||||
$build_command = $this->wrap_build_command_with_env_export("docker build {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} -t {$this->production_image_name} {$this->workdir}");
|
||||
}
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
|
|
@ -3332,7 +3331,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
$this->analyzeBuildTimeVariables($variables);
|
||||
}
|
||||
|
||||
if ($this->dockerBuildkitSupported && $this->application->settings->use_build_secrets) {
|
||||
if ($this->dockerSecretsSupported) {
|
||||
$this->generate_build_secrets($variables);
|
||||
$this->build_args = '';
|
||||
} else {
|
||||
|
|
@ -3819,7 +3818,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||
$this->application_deployment_queue->addLogEntry("Service {$serviceName}: All required ARG declarations already exist.");
|
||||
}
|
||||
|
||||
if ($this->application->settings->use_build_secrets && $this->dockerBuildkitSupported && ! empty($this->build_secrets)) {
|
||||
if ($this->dockerSecretsSupported && ! empty($this->build_secrets)) {
|
||||
$fullDockerfilePath = "{$this->workdir}/{$dockerfilePath}";
|
||||
$this->modify_dockerfile_for_secrets($fullDockerfilePath);
|
||||
$this->application_deployment_queue->addLogEntry("Modified Dockerfile for service {$serviceName} to use build secrets.");
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class ScheduledJobManager implements ShouldQueue
|
|||
|
||||
Log::channel('scheduled')->info('ScheduledJobManager completed', [
|
||||
'execution_time' => $this->executionTime->toIso8601String(),
|
||||
'duration_ms' => Carbon::now()->diffInMilliseconds($this->executionTime),
|
||||
'duration_ms' => $this->executionTime->diffInMilliseconds(Carbon::now()),
|
||||
'dispatched' => $this->dispatchedCount,
|
||||
'skipped' => $this->skippedCount,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -990,7 +990,7 @@ class Application extends BaseModel
|
|||
if (isDev() && data_get($this, 'private_key_id') === 0) {
|
||||
return 'deploy_key';
|
||||
}
|
||||
if (data_get($this, 'private_key_id')) {
|
||||
if (! is_null(data_get($this, 'private_key_id'))) {
|
||||
return 'deploy_key';
|
||||
} elseif (data_get($this, 'source')) {
|
||||
return 'source';
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class ServiceApplication extends BaseModel
|
|||
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
return data_get($this, 'service.environment.project.team');
|
||||
}
|
||||
|
||||
public function workdir()
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class ServiceDatabase extends BaseModel
|
|||
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
return data_get($this, 'service.environment.project.team');
|
||||
}
|
||||
|
||||
public function workdir()
|
||||
|
|
|
|||
|
|
@ -191,7 +191,8 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
|||
$this->getNotificationSettings('discord')?->isEnabled() ||
|
||||
$this->getNotificationSettings('slack')?->isEnabled() ||
|
||||
$this->getNotificationSettings('telegram')?->isEnabled() ||
|
||||
$this->getNotificationSettings('pushover')?->isEnabled();
|
||||
$this->getNotificationSettings('pushover')?->isEnabled() ||
|
||||
$this->getNotificationSettings('webhook')?->isEnabled();
|
||||
}
|
||||
|
||||
public function subscriptionEnded()
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ function allowedPathsForUnsubscribedAccounts()
|
|||
'login',
|
||||
'logout',
|
||||
'force-password-reset',
|
||||
'two-factor-challenge',
|
||||
'livewire/update',
|
||||
'admin',
|
||||
];
|
||||
|
|
@ -95,6 +96,7 @@ function allowedPathsForInvalidAccounts()
|
|||
'logout',
|
||||
'verify',
|
||||
'force-password-reset',
|
||||
'two-factor-challenge',
|
||||
'livewire/update',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
return [
|
||||
'coolify' => [
|
||||
'version' => '4.0.0-beta.463',
|
||||
'version' => '4.0.0-beta.464',
|
||||
'helper_version' => '1.0.12',
|
||||
'realtime_version' => '1.0.10',
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
|
|
|
|||
|
|
@ -184,13 +184,13 @@ return [
|
|||
'connection' => 'redis',
|
||||
'balance' => env('HORIZON_BALANCE', 'false'),
|
||||
'queue' => env('HORIZON_QUEUES', 'high,default'),
|
||||
'maxTime' => 3600,
|
||||
'maxTime' => env('HORIZON_MAX_TIME', 0),
|
||||
'maxJobs' => 400,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'nice' => 0,
|
||||
'sleep' => 3,
|
||||
'timeout' => 3600,
|
||||
'timeout' => env('HORIZON_TIMEOUT', 36000),
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
209
docker-compose-maxio.dev.yml
Normal file
209
docker-compose-maxio.dev.yml
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
services:
|
||||
coolify:
|
||||
image: coolify:dev
|
||||
pull_policy: never
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/development/Dockerfile
|
||||
args:
|
||||
- USER_ID=${USERID:-1000}
|
||||
- GROUP_ID=${GROUPID:-1000}
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:8080"
|
||||
environment:
|
||||
AUTORUN_ENABLED: false
|
||||
PUSHER_HOST: "${PUSHER_HOST}"
|
||||
PUSHER_PORT: "${PUSHER_PORT}"
|
||||
PUSHER_SCHEME: "${PUSHER_SCHEME:-http}"
|
||||
PUSHER_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
healthcheck:
|
||||
test: curl -sf http://127.0.0.1:8080/api/health || exit 1
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
- dev_backups_data:/var/www/html/storage/app/backups
|
||||
networks:
|
||||
- coolify
|
||||
postgres:
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "${FORWARD_DB_PORT:-5432}:5432"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
POSTGRES_USER: "${DB_USERNAME:-coolify}"
|
||||
POSTGRES_PASSWORD: "${DB_PASSWORD:-password}"
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
POSTGRES_HOST_AUTH_METHOD: "trust"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- dev_postgres_data:/var/lib/postgresql/data
|
||||
redis:
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "${FORWARD_REDIS_PORT:-6379}:6379"
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
- dev_redis_data:/data
|
||||
soketi:
|
||||
image: coolify-realtime:dev
|
||||
pull_policy: never
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/coolify-realtime/Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${FORWARD_SOKETI_PORT:-6001}:6001"
|
||||
- "6002:6002"
|
||||
volumes:
|
||||
- ./storage:/var/www/html/storage
|
||||
- ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js
|
||||
environment:
|
||||
SOKETI_DEBUG: "false"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "curl -fsS http://127.0.0.1:6001/ready && curl -fsS http://127.0.0.1:6002/ready || exit 1" ]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
|
||||
vite:
|
||||
image: node:24-alpine
|
||||
pull_policy: always
|
||||
container_name: coolify-vite
|
||||
working_dir: /var/www/html
|
||||
environment:
|
||||
VITE_HOST: "${VITE_HOST:-localhost}"
|
||||
VITE_PORT: "${VITE_PORT:-5173}"
|
||||
ports:
|
||||
- "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
command: sh -c "npm install && npm run dev"
|
||||
networks:
|
||||
- coolify
|
||||
testing-host:
|
||||
image: coolify-testing-host:dev
|
||||
pull_policy: never
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/testing-host/Dockerfile
|
||||
init: true
|
||||
container_name: coolify-testing-host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- dev_coolify_data:/data/coolify
|
||||
- dev_backups_data:/data/coolify/backups
|
||||
- dev_postgres_data:/data/coolify/_volumes/database
|
||||
- dev_redis_data:/data/coolify/_volumes/redis
|
||||
- dev_minio_data:/data/coolify/_volumes/minio
|
||||
networks:
|
||||
- coolify
|
||||
mailpit:
|
||||
image: axllent/mailpit:latest
|
||||
pull_policy: always
|
||||
container_name: coolify-mail
|
||||
ports:
|
||||
- "${FORWARD_MAILPIT_PORT:-1025}:1025"
|
||||
- "${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025"
|
||||
networks:
|
||||
- coolify
|
||||
# maxio:
|
||||
# image: ghcr.io/coollabsio/maxio
|
||||
# pull_policy: always
|
||||
# container_name: coolify-maxio
|
||||
# ports:
|
||||
# - "${FORWARD_MAXIO_PORT:-9000}:9000"
|
||||
# environment:
|
||||
# MAXIO_ACCESS_KEY: "${MAXIO_ACCESS_KEY:-maxioadmin}"
|
||||
# MAXIO_SECRET_KEY: "${MAXIO_SECRET_KEY:-maxioadmin}"
|
||||
# volumes:
|
||||
# - dev_maxio_data:/data
|
||||
# networks:
|
||||
# - coolify
|
||||
minio:
|
||||
image: ghcr.io/coollabsio/minio:RELEASE.2025-10-15T17-29-55Z # Released on 15 October 2025
|
||||
pull_policy: always
|
||||
container_name: coolify-minio
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "${FORWARD_MINIO_PORT:-9000}:9000"
|
||||
- "${FORWARD_MINIO_PORT_CONSOLE:-9001}:9001"
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
|
||||
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
|
||||
volumes:
|
||||
- dev_minio_data:/data
|
||||
- dev_maxio_data:/data
|
||||
networks:
|
||||
- coolify
|
||||
# maxio-init:
|
||||
# image: minio/mc:latest
|
||||
# pull_policy: always
|
||||
# container_name: coolify-maxio-init
|
||||
# restart: no
|
||||
# depends_on:
|
||||
# - maxio
|
||||
# entrypoint: >
|
||||
# /bin/sh -c "
|
||||
# echo 'Waiting for MaxIO to be ready...';
|
||||
# until mc alias set local http://coolify-maxio:9000 maxioadmin maxioadmin 2>/dev/null; do
|
||||
# echo 'MaxIO not ready yet, waiting...';
|
||||
# sleep 2;
|
||||
# done;
|
||||
# echo 'MaxIO is ready, creating bucket if needed...';
|
||||
# mc mb local/local --ignore-existing;
|
||||
# echo 'MaxIO initialization complete - bucket local is ready';
|
||||
# "
|
||||
# networks:
|
||||
# - coolify
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
pull_policy: always
|
||||
container_name: coolify-minio-init
|
||||
restart: no
|
||||
depends_on:
|
||||
- minio
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
echo 'Waiting for MinIO to be ready...';
|
||||
until mc alias set local http://coolify-minio:9000 minioadmin minioadmin 2>/dev/null; do
|
||||
echo 'MinIO not ready yet, waiting...';
|
||||
sleep 2;
|
||||
done;
|
||||
echo 'MinIO is ready, creating bucket if needed...';
|
||||
mc mb local/local --ignore-existing;
|
||||
echo 'MinIO initialization complete - bucket local is ready';
|
||||
"
|
||||
networks:
|
||||
- coolify
|
||||
|
||||
volumes:
|
||||
dev_backups_data:
|
||||
dev_postgres_data:
|
||||
dev_redis_data:
|
||||
dev_coolify_data:
|
||||
dev_minio_data:
|
||||
dev_maxio_data:
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
name: coolify
|
||||
external: false
|
||||
4
public/svgs/electricsql.svg
Normal file
4
public/svgs/electricsql.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="300" height="260" viewBox="0 0 300 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M126.822 31.3968C127.716 30.5005 128.923 30 130.176 30H205.093L98.2708 136.943C97.3776 137.84 96.1702 138.339 94.9168 138.339H20L126.822 31.3968Z" fill="#D0BCFF"/>
|
||||
<path d="M113.684 140.714C113.684 139.399 114.752 138.328 116.063 138.328H205.093L113.684 230V140.714Z" fill="#D0BCFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 401 B |
|
|
@ -25,7 +25,7 @@
|
|||
<path d="M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6" />
|
||||
</svg>
|
||||
{{-- Eye-off icon (shown when password is visible) --}}
|
||||
<svg x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
<svg x-cloak x-show="type === 'text'" xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
|
||||
|
|
|
|||
|
|
@ -3,15 +3,11 @@
|
|||
<div>
|
||||
<p class="font-mono font-semibold text-7xl dark:text-warning">419</p>
|
||||
<h1 class="mt-4 font-bold tracking-tight dark:text-white">This page is definitely old, not like you!</h1>
|
||||
<p class="text-base leading-7 dark:text-neutral-300 text-black">Sorry, we couldn't find the page you're looking
|
||||
for.
|
||||
<p class="text-base leading-7 dark:text-neutral-300 text-black">Your session has expired. Please log in again to continue.
|
||||
</p>
|
||||
<div class="flex items-center mt-10 gap-x-2">
|
||||
<a href="{{ url()->previous() }}">
|
||||
<x-forms.button>Go back</x-forms.button>
|
||||
</a>
|
||||
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||
<x-forms.button>Dashboard</x-forms.button>
|
||||
<a href="/login">
|
||||
<x-forms.button>Back to Login</x-forms.button>
|
||||
</a>
|
||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||
support
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<nav wire:poll.10000ms="checkStatus" class="pb-6">
|
||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||
<div class="navbar-main">
|
||||
<nav class="flex shrink-0 gap-4 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
Configuration
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<h1>{{ $title }}</h1>
|
||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||
<div class="navbar-main" x-data">
|
||||
<nav class="flex shrink-0 gap-4 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||
href="{{ route('project.service.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Route::group([
|
|||
Route::post('/projects/{uuid}/environments', [ProjectController::class, 'create_environment'])->middleware(['api.ability:write']);
|
||||
Route::delete('/projects/{uuid}/environments/{environment_name_or_uuid}', [ProjectController::class, 'delete_environment'])->middleware(['api.ability:write']);
|
||||
|
||||
Route::post('/projects', [ProjectController::class, 'create_project'])->middleware(['api.ability:read']);
|
||||
Route::post('/projects', [ProjectController::class, 'create_project'])->middleware(['api.ability:write']);
|
||||
Route::patch('/projects/{uuid}', [ProjectController::class, 'update_project'])->middleware(['api.ability:write']);
|
||||
Route::delete('/projects/{uuid}', [ProjectController::class, 'delete_project'])->middleware(['api.ability:write']);
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ Route::group([
|
|||
|
||||
Route::get('/servers/{uuid}/validate', [ServersController::class, 'validate_server'])->middleware(['api.ability:read']);
|
||||
|
||||
Route::post('/servers', [ServersController::class, 'create_server'])->middleware(['api.ability:read']);
|
||||
Route::post('/servers', [ServersController::class, 'create_server'])->middleware(['api.ability:write']);
|
||||
Route::patch('/servers/{uuid}', [ServersController::class, 'update_server'])->middleware(['api.ability:write']);
|
||||
Route::delete('/servers/{uuid}', [ServersController::class, 'delete_server'])->middleware(['api.ability:write']);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,26 @@
|
|||
|
||||
services:
|
||||
beszel-agent:
|
||||
image: 'henrygd/beszel-agent:0.16.1' # Released on 14 Nov 2025
|
||||
image: 'henrygd/beszel-agent:0.18.4' # Released on 21 Feb 2026
|
||||
network_mode: host # Network stats graphs won't work if agent cannot access host system network stack
|
||||
environment:
|
||||
# Required
|
||||
- LISTEN=/beszel_socket/beszel.sock
|
||||
- HUB_URL=${HUB_URL?}
|
||||
- 'TOKEN=${TOKEN?}'
|
||||
- 'KEY=${KEY?}'
|
||||
- HUB_URL=$SERVICE_URL_BESZEL
|
||||
- TOKEN=${TOKEN} # From hub token settings
|
||||
- KEY=${KEY} # SSH public key(s) from hub
|
||||
# Optional
|
||||
- DISABLE_SSH=${DISABLE_SSH:-false} # Disable SSH
|
||||
- LOG_LEVEL=${LOG_LEVEL:-warn} # Logging level
|
||||
- SKIP_GPU=${SKIP_GPU:-false} # Skip GPU monitoring
|
||||
- SYSTEM_NAME=${SYSTEM_NAME} # Custom system name
|
||||
volumes:
|
||||
- beszel_agent_data:/var/lib/beszel-agent
|
||||
- beszel_socket:/beszel_socket
|
||||
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
||||
healthcheck:
|
||||
test: ['CMD', '/agent', 'health']
|
||||
interval: 60s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
|
|
@ -9,21 +9,41 @@
|
|||
# Add the public Key in "Key" env variable and token in the "Token" variable below (These are obtained from Beszel UI)
|
||||
services:
|
||||
beszel:
|
||||
image: 'henrygd/beszel:0.16.1' # Released on 14 Nov 2025
|
||||
image: 'henrygd/beszel:0.18.4' # Released on 21 Feb 2026
|
||||
environment:
|
||||
- SERVICE_URL_BESZEL_8090
|
||||
- CONTAINER_DETAILS=${CONTAINER_DETAILS:-true}
|
||||
- SHARE_ALL_SYSTEMS=${SHARE_ALL_SYSTEMS:-false}
|
||||
volumes:
|
||||
- 'beszel_data:/beszel_data'
|
||||
- 'beszel_socket:/beszel_socket'
|
||||
healthcheck:
|
||||
test: ['CMD', '/beszel', 'health', '--url', 'http://localhost:8090']
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
beszel-agent:
|
||||
image: 'henrygd/beszel-agent:0.16.1' # Released on 14 Nov 2025
|
||||
image: 'henrygd/beszel-agent:0.18.4' # Released on 21 Feb 2026
|
||||
network_mode: host # Network stats graphs won't work if agent cannot access host system network stack
|
||||
environment:
|
||||
# Required
|
||||
- LISTEN=/beszel_socket/beszel.sock
|
||||
- HUB_URL=http://beszel:8090
|
||||
- 'TOKEN=${TOKEN}'
|
||||
- 'KEY=${KEY}'
|
||||
- HUB_URL=$SERVICE_URL_BESZEL
|
||||
- TOKEN=${TOKEN} # From hub token settings
|
||||
- KEY=${KEY} # SSH public key(s) from hub
|
||||
# Optional
|
||||
- DISABLE_SSH=${DISABLE_SSH:-false} # Disable SSH
|
||||
- LOG_LEVEL=${LOG_LEVEL:-warn} # Logging level
|
||||
- SKIP_GPU=${SKIP_GPU:-false} # Skip GPU monitoring
|
||||
- SYSTEM_NAME=${SYSTEM_NAME} # Custom system name
|
||||
volumes:
|
||||
- beszel_agent_data:/var/lib/beszel-agent
|
||||
- beszel_socket:/beszel_socket
|
||||
- '/var/run/docker.sock:/var/run/docker.sock:ro'
|
||||
|
||||
healthcheck:
|
||||
test: ['CMD', '/agent', 'health']
|
||||
interval: 60s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
27
templates/compose/electricsql.yaml
Normal file
27
templates/compose/electricsql.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# documentation: https://electric-sql.com/docs/guides/deployment
|
||||
# slogan: Sync shape-based subsets of your Postgres data over HTTP.
|
||||
# category: backend
|
||||
# tags: electric,electricsql,realtime,sync,postgresql
|
||||
# logo: svgs/electricsql.svg
|
||||
# port: 3000
|
||||
|
||||
## This template intentionally does not deploy PostgreSQL.
|
||||
## Set DATABASE_URL to an existing Postgres instance with logical replication enabled.
|
||||
## If ELECTRIC_SECRET is set, your own backend/proxy must append it to shape requests.
|
||||
|
||||
services:
|
||||
electric:
|
||||
image: electricsql/electric:1.4.2
|
||||
environment:
|
||||
- SERVICE_URL_ELECTRIC_3000
|
||||
- DATABASE_URL=${DATABASE_URL:?}
|
||||
- ELECTRIC_SECRET=${SERVICE_PASSWORD_64_ELECTRIC}
|
||||
- ELECTRIC_STORAGE_DIR=/app/persistent
|
||||
- ELECTRIC_USAGE_REPORTING=${ELECTRIC_USAGE_REPORTING:-false}
|
||||
volumes:
|
||||
- electric_data:/app/persistent
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/v1/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# ignore: true
|
||||
# documentation: https://docs.plane.so/self-hosting/methods/docker-compose
|
||||
# slogan: The open source project management tool
|
||||
# category: productivity
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# ignore: true
|
||||
# documentation: https://pterodactyl.io/
|
||||
# slogan: Pterodactyl is a free, open-source game server management panel
|
||||
# category: media
|
||||
|
|
@ -102,4 +103,4 @@ services:
|
|||
- MAIL_PORT=$MAIL_PORT
|
||||
- MAIL_USERNAME=$MAIL_USERNAME
|
||||
- MAIL_PASSWORD=$MAIL_PASSWORD
|
||||
- MAIL_ENCRYPTION=$MAIL_ENCRYPTION
|
||||
- MAIL_ENCRYPTION=$MAIL_ENCRYPTION
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# ignore: true
|
||||
# documentation: https://pterodactyl.io/
|
||||
# slogan: Pterodactyl is a free, open-source game server management panel
|
||||
# category: media
|
||||
|
|
|
|||
75
tests/Feature/ApiTokenPermissionTest.php
Normal file
75
tests/Feature/ApiTokenPermissionTest.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->team = Team::factory()->create();
|
||||
$this->user = User::factory()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
|
||||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
describe('POST /api/v1/projects', function () {
|
||||
test('read-only token cannot create a project', function () {
|
||||
$token = $this->user->createToken('read-only', ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token->plainTextToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/projects', [
|
||||
'name' => 'Test Project',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
|
||||
test('write token can create a project', function () {
|
||||
$token = $this->user->createToken('write-token', ['write']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token->plainTextToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/projects', [
|
||||
'name' => 'Test Project',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonStructure(['uuid']);
|
||||
});
|
||||
|
||||
test('root token can create a project', function () {
|
||||
$token = $this->user->createToken('root-token', ['root']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token->plainTextToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/projects', [
|
||||
'name' => 'Test Project',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
$response->assertJsonStructure(['uuid']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/v1/servers', function () {
|
||||
test('read-only token cannot create a server', function () {
|
||||
$token = $this->user->createToken('read-only', ['read']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer '.$token->plainTextToken,
|
||||
'Content-Type' => 'application/json',
|
||||
])->postJson('/api/v1/servers', [
|
||||
'name' => 'Test Server',
|
||||
'ip' => '1.2.3.4',
|
||||
'private_key_uuid' => 'fake-uuid',
|
||||
]);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,144 +1,59 @@
|
|||
<?php
|
||||
|
||||
test('multiline environment variables are properly escaped for docker build args', function () {
|
||||
$sshKey = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----';
|
||||
|
||||
test('generateDockerBuildArgs returns only keys without values', function () {
|
||||
$variables = [
|
||||
['key' => 'SSH_PRIVATE_KEY', 'value' => "'{$sshKey}'", 'is_multiline' => true],
|
||||
['key' => 'SSH_PRIVATE_KEY', 'value' => "'some-ssh-key'", 'is_multiline' => true],
|
||||
['key' => 'REGULAR_VAR', 'value' => 'simple value', 'is_multiline' => false],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
|
||||
// SSH key should use double quotes and have proper escaping
|
||||
$sshArg = $buildArgs->first();
|
||||
expect($sshArg)->toStartWith('--build-arg SSH_PRIVATE_KEY="');
|
||||
expect($sshArg)->toEndWith('"');
|
||||
expect($sshArg)->toContain('BEGIN OPENSSH PRIVATE KEY');
|
||||
expect($sshArg)->not->toContain("'BEGIN"); // Should not have the wrapper single quotes
|
||||
|
||||
// Regular var should use escapeshellarg (single quotes)
|
||||
$regularArg = $buildArgs->last();
|
||||
expect($regularArg)->toBe("--build-arg REGULAR_VAR='simple value'");
|
||||
// Docker gets values from the environment, so only keys should be in build args
|
||||
expect($buildArgs->first())->toBe('--build-arg SSH_PRIVATE_KEY');
|
||||
expect($buildArgs->last())->toBe('--build-arg REGULAR_VAR');
|
||||
});
|
||||
|
||||
test('multiline variables with special bash characters are escaped correctly', function () {
|
||||
$valueWithSpecialChars = "line1\nline2 with \"quotes\"\nline3 with \$variables\nline4 with `backticks`";
|
||||
test('generateDockerBuildArgs works with collection of objects', function () {
|
||||
$variables = collect([
|
||||
(object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
|
||||
(object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
|
||||
]);
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
expect($buildArgs)->toHaveCount(2);
|
||||
expect($buildArgs->values()->toArray())->toBe([
|
||||
'--build-arg VAR1',
|
||||
'--build-arg VAR2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('generateDockerBuildArgs collection can be imploded into valid command string', function () {
|
||||
$variables = [
|
||||
['key' => 'SPECIAL_VALUE', 'value' => "'{$valueWithSpecialChars}'", 'is_multiline' => true],
|
||||
['key' => 'COOLIFY_URL', 'value' => 'http://example.com', 'is_multiline' => false],
|
||||
['key' => 'COOLIFY_BRANCH', 'value' => 'main', 'is_multiline' => false],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
|
||||
// The collection must be imploded to a string for command interpolation
|
||||
// This was the bug: Collection was interpolated as JSON instead of a space-separated string
|
||||
$argsString = $buildArgs->implode(' ');
|
||||
expect($argsString)->toBe('--build-arg COOLIFY_URL --build-arg COOLIFY_BRANCH');
|
||||
|
||||
// Verify it does NOT produce JSON when cast to string
|
||||
expect($argsString)->not->toContain('{');
|
||||
expect($argsString)->not->toContain('}');
|
||||
});
|
||||
|
||||
test('generateDockerBuildArgs handles variables without is_multiline', function () {
|
||||
$variables = [
|
||||
['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Verify double quotes are escaped
|
||||
expect($arg)->toContain('\\"quotes\\"');
|
||||
// Verify dollar signs are escaped
|
||||
expect($arg)->toContain('\\$variables');
|
||||
// Verify backticks are escaped
|
||||
expect($arg)->toContain('\\`backticks\\`');
|
||||
});
|
||||
|
||||
test('single-line environment variables use escapeshellarg', function () {
|
||||
$variables = [
|
||||
['key' => 'SIMPLE_VAR', 'value' => 'simple value with spaces', 'is_multiline' => false],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Should use single quotes from escapeshellarg
|
||||
expect($arg)->toBe("--build-arg SIMPLE_VAR='simple value with spaces'");
|
||||
});
|
||||
|
||||
test('multiline certificate with newlines is preserved', function () {
|
||||
$certificate = '-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRkSvMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTkwOTE3MDUzMzI5WhcNMjkwOTE0MDUzMzI5WjBF
|
||||
-----END CERTIFICATE-----';
|
||||
|
||||
$variables = [
|
||||
['key' => 'TLS_CERT', 'value' => "'{$certificate}'", 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Newlines should be preserved in the output
|
||||
expect($arg)->toContain("\n");
|
||||
expect($arg)->toContain('BEGIN CERTIFICATE');
|
||||
expect($arg)->toContain('END CERTIFICATE');
|
||||
expect(substr_count($arg, "\n"))->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('multiline JSON configuration is properly escaped', function () {
|
||||
$jsonConfig = '{
|
||||
"key": "value",
|
||||
"nested": {
|
||||
"array": [1, 2, 3]
|
||||
}
|
||||
}';
|
||||
|
||||
$variables = [
|
||||
['key' => 'JSON_CONFIG', 'value' => "'{$jsonConfig}'", 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// All double quotes in JSON should be escaped
|
||||
expect($arg)->toContain('\\"key\\"');
|
||||
expect($arg)->toContain('\\"value\\"');
|
||||
expect($arg)->toContain('\\"nested\\"');
|
||||
});
|
||||
|
||||
test('empty multiline variable is handled correctly', function () {
|
||||
$variables = [
|
||||
['key' => 'EMPTY_VAR', 'value' => "''", 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
expect($arg)->toBe('--build-arg EMPTY_VAR=""');
|
||||
});
|
||||
|
||||
test('multiline variable with only newlines', function () {
|
||||
$onlyNewlines = "\n\n\n";
|
||||
|
||||
$variables = [
|
||||
['key' => 'NEWLINES_ONLY', 'value' => "'{$onlyNewlines}'", 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
expect($arg)->toContain("\n");
|
||||
// Should have 3 newlines preserved
|
||||
expect(substr_count($arg, "\n"))->toBe(3);
|
||||
});
|
||||
|
||||
test('multiline variable with backslashes is escaped correctly', function () {
|
||||
$valueWithBackslashes = "path\\to\\file\nC:\\Windows\\System32";
|
||||
|
||||
$variables = [
|
||||
['key' => 'PATH_VAR', 'value' => "'{$valueWithBackslashes}'", 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Backslashes should be doubled
|
||||
expect($arg)->toContain('path\\\\to\\\\file');
|
||||
expect($arg)->toContain('C:\\\\Windows\\\\System32');
|
||||
expect($arg)->toBe('--build-arg NO_FLAG_VAR');
|
||||
});
|
||||
|
||||
test('generateDockerEnvFlags produces correct format', function () {
|
||||
|
|
@ -155,54 +70,14 @@ test('generateDockerEnvFlags produces correct format', function () {
|
|||
expect($envFlags)->toContain('line2');
|
||||
});
|
||||
|
||||
test('helper functions work with collection input', function () {
|
||||
test('generateDockerEnvFlags works with collection input', function () {
|
||||
$variables = collect([
|
||||
(object) ['key' => 'VAR1', 'value' => 'value1', 'is_multiline' => false],
|
||||
(object) ['key' => 'VAR2', 'value' => "'multiline\nvalue'", 'is_multiline' => true],
|
||||
]);
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
expect($buildArgs)->toHaveCount(2);
|
||||
|
||||
$envFlags = generateDockerEnvFlags($variables);
|
||||
expect($envFlags)->toBeString();
|
||||
expect($envFlags)->toContain('-e VAR1=');
|
||||
expect($envFlags)->toContain('-e VAR2="');
|
||||
});
|
||||
|
||||
test('variables without is_multiline default to false', function () {
|
||||
$variables = [
|
||||
['key' => 'NO_FLAG_VAR', 'value' => 'some value'],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Should use escapeshellarg (single quotes) since is_multiline defaults to false
|
||||
expect($arg)->toBe("--build-arg NO_FLAG_VAR='some value'");
|
||||
});
|
||||
|
||||
test('real world SSH key example', function () {
|
||||
// Simulate what real_value returns (wrapped in single quotes)
|
||||
$sshKey = "'-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----'";
|
||||
|
||||
$variables = [
|
||||
['key' => 'KEY', 'value' => $sshKey, 'is_multiline' => true],
|
||||
];
|
||||
|
||||
$buildArgs = generateDockerBuildArgs($variables);
|
||||
$arg = $buildArgs->first();
|
||||
|
||||
// Should produce clean output without wrapper quotes
|
||||
expect($arg)->toStartWith('--build-arg KEY="-----BEGIN OPENSSH PRIVATE KEY-----');
|
||||
expect($arg)->toEndWith('-----END OPENSSH PRIVATE KEY-----"');
|
||||
// Should NOT have the escaped quote sequence that was in the bug
|
||||
expect($arg)->not->toContain("''");
|
||||
expect($arg)->not->toContain("'\\''");
|
||||
});
|
||||
|
|
|
|||
77
tests/Feature/ServiceDatabaseTeamTest.php
Normal file
77
tests/Feature/ServiceDatabaseTeamTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('returns the correct team through the service relationship chain', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$project = Project::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
$environment = Environment::create([
|
||||
'name' => 'test-env-'.Illuminate\Support\Str::random(8),
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
|
||||
$service = Service::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'supabase',
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => 1,
|
||||
'destination_type' => 'App\Models\StandaloneDocker',
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$serviceDatabase = ServiceDatabase::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'supabase-db',
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
|
||||
expect($serviceDatabase->team())->not->toBeNull()
|
||||
->and($serviceDatabase->team()->id)->toBe($team->id);
|
||||
});
|
||||
|
||||
it('returns the correct team for ServiceApplication through the service relationship chain', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$project = Project::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
$environment = Environment::create([
|
||||
'name' => 'test-env-'.Illuminate\Support\Str::random(8),
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
|
||||
$service = Service::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'supabase',
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => 1,
|
||||
'destination_type' => 'App\Models\StandaloneDocker',
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$serviceApplication = ServiceApplication::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
'name' => 'supabase-studio',
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
|
||||
expect($serviceApplication->team())->not->toBeNull()
|
||||
->and($serviceApplication->team()->id)->toBe($team->id);
|
||||
});
|
||||
52
tests/Feature/TeamNotificationCheckTest.php
Normal file
52
tests/Feature/TeamNotificationCheckTest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->team = Team::factory()->create();
|
||||
});
|
||||
|
||||
describe('isAnyNotificationEnabled', function () {
|
||||
test('returns false when no notifications are enabled', function () {
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeFalse();
|
||||
});
|
||||
|
||||
test('returns true when email notifications are enabled', function () {
|
||||
$this->team->emailNotificationSettings->update(['smtp_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when discord notifications are enabled', function () {
|
||||
$this->team->discordNotificationSettings->update(['discord_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when slack notifications are enabled', function () {
|
||||
$this->team->slackNotificationSettings->update(['slack_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when telegram notifications are enabled', function () {
|
||||
$this->team->telegramNotificationSettings->update(['telegram_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when pushover notifications are enabled', function () {
|
||||
$this->team->pushoverNotificationSettings->update(['pushover_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('returns true when webhook notifications are enabled', function () {
|
||||
$this->team->webhookNotificationSettings->update(['webhook_enabled' => true]);
|
||||
|
||||
expect($this->team->isAnyNotificationEnabled())->toBeTrue();
|
||||
});
|
||||
});
|
||||
65
tests/Feature/TwoFactorChallengeAccessTest.php
Normal file
65
tests/Feature/TwoFactorChallengeAccessTest.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = Team::factory()->personal()->create();
|
||||
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
||||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
it('allows unauthenticated access to two-factor-challenge page', function () {
|
||||
$response = $this->get('/two-factor-challenge');
|
||||
|
||||
// Fortify returns a redirect to /login if there's no login.id in session,
|
||||
// but the important thing is it does NOT return a 419 or 500
|
||||
expect($response->status())->toBeIn([200, 302]);
|
||||
});
|
||||
|
||||
it('includes two-factor-challenge in allowed paths for unsubscribed accounts', function () {
|
||||
$paths = allowedPathsForUnsubscribedAccounts();
|
||||
|
||||
expect($paths)->toContain('two-factor-challenge');
|
||||
});
|
||||
|
||||
it('includes two-factor-challenge in allowed paths for invalid accounts', function () {
|
||||
$paths = allowedPathsForInvalidAccounts();
|
||||
|
||||
expect($paths)->toContain('two-factor-challenge');
|
||||
});
|
||||
|
||||
it('includes two-factor-challenge in allowed paths for boarding accounts', function () {
|
||||
$paths = allowedPathsForBoardingAccounts();
|
||||
|
||||
expect($paths)->toContain('two-factor-challenge');
|
||||
});
|
||||
|
||||
it('does not redirect authenticated user with force_password_reset from two-factor-challenge', function () {
|
||||
$this->user->update(['force_password_reset' => true]);
|
||||
|
||||
$response = $this->actingAs($this->user)->get('/two-factor-challenge');
|
||||
|
||||
// Should NOT redirect to force-password-reset page
|
||||
if ($response->isRedirect()) {
|
||||
expect($response->headers->get('Location'))->not->toContain('force-password-reset');
|
||||
}
|
||||
});
|
||||
|
||||
it('renders 419 error page with login link instead of previous url', function () {
|
||||
$response = $this->get('/two-factor-challenge', [
|
||||
'X-CSRF-TOKEN' => 'invalid-token',
|
||||
]);
|
||||
|
||||
// The 419 page should exist and contain a link to /login
|
||||
$view = view('errors.419')->render();
|
||||
|
||||
expect($view)->toContain('/login');
|
||||
expect($view)->toContain('Back to Login');
|
||||
expect($view)->toContain('This page is definitely old, not like you!');
|
||||
expect($view)->not->toContain('url()->previous()');
|
||||
});
|
||||
11
tests/Unit/ApplicationDeploymentTypeTest.php
Normal file
11
tests/Unit/ApplicationDeploymentTypeTest.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
|
||||
it('treats zero private key id as deploy key', function () {
|
||||
$application = new Application();
|
||||
$application->private_key_id = 0;
|
||||
$application->source = null;
|
||||
|
||||
expect($application->deploymentType())->toBe('deploy_key');
|
||||
});
|
||||
52
tests/Unit/StartKeydbConfigPermissionTest.php
Normal file
52
tests/Unit/StartKeydbConfigPermissionTest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Database\StartKeydb;
|
||||
use App\Models\StandaloneKeydb;
|
||||
|
||||
test('keydb config chown command is added when keydb_conf is set', function () {
|
||||
$action = new StartKeydb;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneKeydb::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('keydb_conf')->andReturn('maxmemory 2gb');
|
||||
$action->database = $database;
|
||||
|
||||
if (! is_null($action->database->keydb_conf) && ! empty($action->database->keydb_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/keydb.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toContain('chown 999:999 /data/coolify/databases/test-uuid/keydb.conf');
|
||||
});
|
||||
|
||||
test('keydb config chown command is not added when keydb_conf is null', function () {
|
||||
$action = new StartKeydb;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneKeydb::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('keydb_conf')->andReturn(null);
|
||||
$action->database = $database;
|
||||
|
||||
if (! is_null($action->database->keydb_conf) && ! empty($action->database->keydb_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/keydb.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toBeEmpty();
|
||||
});
|
||||
|
||||
test('keydb config chown command is not added when keydb_conf is empty', function () {
|
||||
$action = new StartKeydb;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneKeydb::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('keydb_conf')->andReturn('');
|
||||
$action->database = $database;
|
||||
|
||||
if (! is_null($action->database->keydb_conf) && ! empty($action->database->keydb_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/keydb.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toBeEmpty();
|
||||
});
|
||||
53
tests/Unit/StartRedisConfigPermissionTest.php
Normal file
53
tests/Unit/StartRedisConfigPermissionTest.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Models\StandaloneRedis;
|
||||
|
||||
test('redis config chown command is added when redis_conf is set', function () {
|
||||
$action = new StartRedis;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneRedis::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('redis_conf')->andReturn('maxmemory 2gb');
|
||||
$action->database = $database;
|
||||
|
||||
// Simulate the chown logic from handle()
|
||||
if (! is_null($action->database->redis_conf) && ! empty($action->database->redis_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/redis.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toContain('chown 999:999 /data/coolify/databases/test-uuid/redis.conf');
|
||||
});
|
||||
|
||||
test('redis config chown command is not added when redis_conf is null', function () {
|
||||
$action = new StartRedis;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneRedis::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('redis_conf')->andReturn(null);
|
||||
$action->database = $database;
|
||||
|
||||
if (! is_null($action->database->redis_conf) && ! empty($action->database->redis_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/redis.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toBeEmpty();
|
||||
});
|
||||
|
||||
test('redis config chown command is not added when redis_conf is empty', function () {
|
||||
$action = new StartRedis;
|
||||
$action->configuration_dir = '/data/coolify/databases/test-uuid';
|
||||
$action->commands = [];
|
||||
|
||||
$database = Mockery::mock(StandaloneRedis::class)->makePartial();
|
||||
$database->shouldReceive('getAttribute')->with('redis_conf')->andReturn('');
|
||||
$action->database = $database;
|
||||
|
||||
if (! is_null($action->database->redis_conf) && ! empty($action->database->redis_conf)) {
|
||||
$action->commands[] = "chown 999:999 {$action->configuration_dir}/redis.conf";
|
||||
}
|
||||
|
||||
expect($action->commands)->toBeEmpty();
|
||||
});
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"coolify": {
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.463"
|
||||
"version": "4.0.0-beta.464"
|
||||
},
|
||||
"nightly": {
|
||||
"version": "4.0.0-beta.464"
|
||||
"version": "4.0.0-beta.465"
|
||||
},
|
||||
"helper": {
|
||||
"version": "1.0.12"
|
||||
|
|
|
|||
Loading…
Reference in a new issue