Compare commits

...

188 Commits

Author SHA1 Message Date
Andras Bacsai
094dfde048 Merge pull request #2090 from coollabsio/next
v4.0.0-beta.270
2024-04-28 11:21:00 +02:00
Andras Bacsai
714b887274 Fix database_name() method to return null instead of "???" 2024-04-28 11:18:37 +02:00
Andras Bacsai
8cfa88eff8 Merge pull request #2084 from iamEvanYT/backup-fix
fix: MongoDB backups failing
2024-04-28 11:18:15 +02:00
Andras Bacsai
3976b57100 fix: mongo db backup 2024-04-28 11:17:20 +02:00
iamEvan
bbcf484f7f Fix MongoDB Backups failing! 2024-04-27 11:22:58 +01:00
Andras Bacsai
249f35f948 Merge pull request #2078 from coollabsio/next
v4.0.0-beta.269
2024-04-26 21:12:57 +02:00
Andras Bacsai
b71f1a79c8 revert: variable parsing 2024-04-26 21:11:40 +02:00
Andras Bacsai
99f57269fb Update version to 4.0.0-beta.269 2024-04-26 15:38:04 +02:00
Andras Bacsai
93def3a557 Merge pull request #2076 from coollabsio/next
v4.0.0-beta.268
2024-04-26 15:19:38 +02:00
Andras Bacsai
a04674d93d Merge pull request #2006 from grahamhealy20/add-mono-font-to-textarea
Text areas use monospaced fonts with spellcheck disabled by default
2024-04-26 15:16:59 +02:00
Andras Bacsai
539cc187a8 Merge pull request #2033 from jere-co/update-service-docs-url
fix: Update service contribution docs URL
2024-04-26 15:15:23 +02:00
Andras Bacsai
fcdd975751 Merge pull request #2028 from duarteocarmo/fix-domains-message
Fix domains example
2024-04-26 15:13:41 +02:00
Andras Bacsai
8a4c2bf208 shared variables are more visible now on the ui 2024-04-26 14:59:03 +02:00
Andras Bacsai
50c5d533b0 Fix condition for displaying environment variable in show.blade.php 2024-04-26 14:13:32 +02:00
Andras Bacsai
f952553c76 fix: move s3 storages to separate view 2024-04-26 14:09:54 +02:00
Andras Bacsai
9a9be466f7 Refactor replaceVariables function in services.php to use a more concise syntax 2024-04-26 13:49:38 +02:00
Andras Bacsai
bbad029aa1 Update version to 4.0.0-beta.268 2024-04-26 12:59:54 +02:00
Andras Bacsai
eb748554c5 Fix environment variable generation in ApplicationDeploymentJob.php and Application.php 2024-04-26 12:59:51 +02:00
Andras Bacsai
c8b494e909 Add Odoo service and template files 2024-04-26 12:05:56 +02:00
Andras Bacsai
bd7fd725f6 Merge pull request #2068 from coollabsio/next
v4.0.0-beta.267
2024-04-26 10:37:47 +02:00
Andras Bacsai
3e0440ba53 feat: initial datalist 2024-04-26 10:33:55 +02:00
Andras Bacsai
83a29f8d85 Refactor parseDockerComposeFile function in shared.php to escape service labels with escapeDollarSign 2024-04-26 09:32:47 +02:00
Andras Bacsai
96883dabe3 fix tags + ui 2024-04-25 15:04:49 +02:00
Andras Bacsai
da521020f6 Fix styling issues and add new badge class in app.css 2024-04-25 14:51:25 +02:00
Andras Bacsai
ac78738061 Fix styling issues in index.blade.php 2024-04-25 14:32:37 +02:00
Andras Bacsai
b78ca71c0f Fix disabling of environment variables starting with SERVICE_FQDN or SERVICE_URL in Show.php 2024-04-25 14:29:23 +02:00
Andras Bacsai
cd7272d00a Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 14:15:44 +02:00
Andras Bacsai
cd7489c569 Update version.php to 4.0.0-beta.267 2024-04-25 14:12:43 +02:00
Andras Bacsai
1f3f369df0 Merge branch 'main' into next 2024-04-25 13:55:57 +02:00
Andras Bacsai
911e29f2ea fix: refresh public ips on start 2024-04-25 13:52:52 +02:00
Andras Bacsai
d7b359e432 Fix cleanup of stuck resources in CleanupStuckedResources.php 2024-04-25 13:44:36 +02:00
Andras Bacsai
0b2c92cbe5 able to change storage name and path 2024-04-25 13:37:45 +02:00
Andras Bacsai
f626d964c5 Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 13:25:35 +02:00
Andras Bacsai
b0e0b38549 Merge pull request #2037 from stefankommm/fix-branch-name
Fix: Encapsulated branch name when cloning branch
2024-04-25 13:16:06 +02:00
Andras Bacsai
94af4b2c7b Add pull request template with instructions to use 'next' branch as destination 2024-04-25 13:15:29 +02:00
Andras Bacsai
0b235e146f Fix readonly attribute in source.blade.php 2024-04-25 13:10:54 +02:00
Andras Bacsai
40f3301324 Fix remove dropzone script async attribute 2024-04-25 13:10:50 +02:00
Andras Bacsai
4fe90e59a8 Fix formatting issue in select.blade.php 2024-04-25 13:01:40 +02:00
Andras Bacsai
b28611ed08 Fix typo in create-scheduled-backup.blade.php 2024-04-25 12:59:25 +02:00
Andras Bacsai
672ad22e4f fix: n8n template 2024-04-25 12:32:35 +02:00
Andras Bacsai
c17bd5ec3a fix: able to select server when creating new destination 2024-04-25 12:14:02 +02:00
Andras Bacsai
2770755f9d feat: add db name to backup notifications 2024-04-25 12:09:46 +02:00
Andras Bacsai
ee69cdbf7b fix: 500 error on edge case 2024-04-25 12:09:36 +02:00
Andras Bacsai
a72a25640f fix: do no able to delete gh app without deleting resources 2024-04-25 11:53:06 +02:00
Andras Bacsai
e86f8ddd09 fix: caddy network issue
feat: connect compose based apps to predefined network
2024-04-25 11:49:34 +02:00
Andras Bacsai
66bdb39f1a Update version numbers to 4.0.0-beta.267 2024-04-24 18:03:20 +02:00
Graham Healy
aa368c3a63 Merge branch 'next' into add-mono-font-to-textarea 2024-04-24 16:20:23 +01:00
Andras Bacsai
b499a2ab87 Merge pull request #2027 from coollabsio/next
v4.0.0-beta.266
2024-04-24 14:35:04 +02:00
Andras Bacsai
49e4482947 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:34:03 +02:00
Andras Bacsai
6e7e4250e6 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:33:20 +02:00
Andras Bacsai
5a8ea15c4f Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-24 14:32:34 +02:00
Marek Štefanko
f9aa029e8e Added " " to encapsulate branch name when cloning 2024-04-20 00:10:50 +02:00
Jere Salonen
6653e379b3 feat: Update service contribution docs URL
Updated the URL in the service contribution docs to point to the correct
knowledge base page for adding a new service.
2024-04-19 09:57:46 +02:00
Duarte OC
930a611374 Fix domains example 2024-04-18 18:20:51 +02:00
Andras Bacsai
51e918be6d Fix issue with pulling helper image in Kernel.php and ApplicationDeploymentJob.php 2024-04-18 14:01:35 +02:00
Andras Bacsai
b0170eea8f Update version numbers to 4.0.0-beta.266 2024-04-18 14:01:29 +02:00
Andras Bacsai
144b34ca2e Merge pull request #2025 from coollabsio/next
v4.0.0-beta.265
2024-04-18 13:54:23 +02:00
Andras Bacsai
676c022e41 fix: .env saved to deployment server, not to build server 2024-04-18 13:53:10 +02:00
Andras Bacsai
7c0b98bb70 Fix sorting issue in save_environment_variables() function 2024-04-18 12:37:06 +02:00
Andras Bacsai
7779713392 Fix issue with user validation in Server.php 2024-04-18 11:48:10 +02:00
Andras Bacsai
5e1396025c Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-18 11:41:58 +02:00
Andras Bacsai
d09c88f71c Fix formatting issue in deployment show.blade.php 2024-04-18 11:14:26 +02:00
Andras Bacsai
7980f21b1b Update version numbers to 4.0.0-beta.265 2024-04-18 11:14:23 +02:00
Andras Bacsai
3843994a05 Merge pull request #2022 from coollabsio/next
v4.0.0-beta.264
2024-04-18 06:58:01 +02:00
Andras Bacsai
a1b08ca037 revert .env changes 2024-04-18 06:55:47 +02:00
Andras Bacsai
144cdd11ec Update version numbers to 4.0.0-beta.264 2024-04-18 06:55:40 +02:00
Andras Bacsai
b6531cdb10 Merge pull request #2016 from coollabsio/next
v4.0.0-beta.262
2024-04-17 18:50:54 +02:00
Andras Bacsai
0eef4a5fa1 Fix DNS validation issue and check domain usage 2024-04-17 18:49:31 +02:00
Andras Bacsai
42baaf8f2d Update version numbers to 4.0.0-beta.263 2024-04-17 18:49:25 +02:00
Andras Bacsai
08d9dff8eb Merge pull request #2014 from coollabsio/next
v4.0.0-beta.262
2024-04-17 16:47:14 +02:00
Andras Bacsai
01b3aab9bc fix: $ in labels escaped 2024-04-17 16:46:12 +02:00
Andras Bacsai
fde34ef178 version++ 2024-04-17 16:46:00 +02:00
Andras Bacsai
5195abec94 Update slogans in appsmith.yaml, appwrite.yaml, babybuddy.yaml, and budge.yaml 2024-04-17 15:48:14 +02:00
Andras Bacsai
aa79acd09e Merge pull request #2012 from coollabsio/next
v4.0.0-beta.261
2024-04-17 15:34:44 +02:00
Andras Bacsai
67011ccd72 fix: base64 encode .env 2024-04-17 15:34:31 +02:00
Andras Bacsai
e2cd7fe17e fix: rollback feature 2024-04-17 15:30:08 +02:00
Andras Bacsai
4c0624f489 fix: remove lazy loading until bug figured out 2024-04-17 15:16:08 +02:00
Andras Bacsai
8c033250b1 Update version numbers to 4.0.0-beta.261 2024-04-17 14:27:53 +02:00
Andras Bacsai
05b5a6fddf Update healthcheck URLs to use 127.0.0.1 instead of localhost 2024-04-17 14:27:51 +02:00
Andras Bacsai
df5bde7b8e Merge pull request #2008 from coollabsio/next
v4.0.0-beta.260
2024-04-17 12:52:09 +02:00
Andras Bacsai
ca5f52c48c Add Tolgee service to compose file and service templates 2024-04-17 12:48:11 +02:00
Andras Bacsai
3656adf059 Add Tolgee authentication fields to Service.php 2024-04-17 12:48:04 +02:00
Andras Bacsai
4381792b05 Refactor variable name display in stack-form.blade.php 2024-04-17 12:48:01 +02:00
Andras Bacsai
0a3c20b08a Fix issue with sorting environment variables in StackForm.php 2024-04-17 12:47:57 +02:00
Andras Bacsai
4ab3f41665 Remove debug statement in remoteProcess.php 2024-04-17 12:47:45 +02:00
=
c75ce9cbba Removing extra whitespace 2024-04-17 11:14:29 +01:00
Andras Bacsai
10356a4376 Fix issue with sorting environment variables in ApplicationDeploymentJob.php 2024-04-17 12:13:44 +02:00
=
4fb4e19e99 Using corrrect ternary pattern to generate textarea spellcheck attribute 2024-04-17 11:12:15 +01:00
Andras Bacsai
98c2056f53 Fix issue with saving environment variables and update version numbers 2024-04-17 12:08:35 +02:00
Andras Bacsai
74e8ae4d79 Merge pull request #1982 from coollabsio/next
v4.0.0-beta.259
2024-04-17 11:43:10 +02:00
Andras Bacsai
6b890cf3b3 Update user input label and add experimental note in by-ip.blade.php 2024-04-17 11:41:46 +02:00
Andras Bacsai
d62b471afe Refactor dispatch event names to use snake_case in General.php, FileStorage.php, Storage.php, All.php, and Show.php 2024-04-17 11:32:35 +02:00
Andras Bacsai
b97240963e Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-17 11:23:54 +02:00
Andras Bacsai
55ed0f2374 fix: remove temporary cloudflared config 2024-04-17 11:22:43 +02:00
Andras Bacsai
2c1b3d5790 Update Supabase Studio image version in supabase.yaml 2024-04-17 11:13:23 +02:00
Andras Bacsai
9c3b757578 fix: respect .env file (for default values) 2024-04-17 11:13:20 +02:00
Andras Bacsai
bc04ad96b5 Refactor Redis connection URLs in StandaloneDragonfly.php 2024-04-17 10:54:55 +02:00
Andras Bacsai
35b07a9c18 Refactor code to add sudo prefix for certain commands in multiple files 2024-04-17 10:49:34 +02:00
Andras Bacsai
17955fc419 Refactor error message styling in deployment show.blade.php 2024-04-17 10:49:30 +02:00
Andras Bacsai
ac98f5862a Refactor environment variable helper text in blade files 2024-04-17 10:49:24 +02:00
Andras Bacsai
63758c6679 Remove debug statement in ExecuteRemoteCommand trait 2024-04-16 21:30:28 +02:00
Andras Bacsai
7c765c47e6 Refactor user input in by-ip.blade.php 2024-04-16 21:30:10 +02:00
Andras Bacsai
58523b0000 Refactor code to add sudo prefix for certain commands 2024-04-16 21:22:57 +02:00
Andras Bacsai
41f4b36593 wip: non-root 2024-04-16 20:57:54 +02:00
=
379212b8fe Making text areas use monospaced font and disabling spellcheck by default 2024-04-16 17:29:09 +01:00
Andras Bacsai
a04c7831b1 wip: non-root user for remote servers 2024-04-16 15:42:38 +02:00
Andras Bacsai
1aec16a240 Refactor healthcheck configurations in Docker Compose files 2024-04-16 14:08:11 +02:00
Andras Bacsai
498626748d Update Supabase images to latest versions 2024-04-16 13:18:55 +02:00
Andras Bacsai
f24ec97607 Refactor ActivityMonitor component to fix max height option 2024-04-16 13:18:50 +02:00
Andras Bacsai
f0ad260eab Refactor ActivityMonitor component to add max height option 2024-04-16 13:14:09 +02:00
Andras Bacsai
92ee2be021 Merge pull request #1832 from estubmo/main
Upgrade Appwrite service template to 1.5
2024-04-16 12:56:54 +02:00
Andras Bacsai
02e4528f3e add and fix a lot of services 2024-04-16 12:42:12 +02:00
Andras Bacsai
f871353acc Refactor modal-input.blade.php and navbar.blade.php to improve code readability and maintainability 2024-04-16 12:41:44 +02:00
Andras Bacsai
faa1120e14 Refactor Show.php and docker.php to improve code readability and maintainability 2024-04-16 12:41:33 +02:00
Andras Bacsai
8a8000c80a Add Unleash service configuration to Service model 2024-04-16 12:41:28 +02:00
Andras Bacsai
8164610105 Refactor EditDomain.php and shared.php to improve DockerCompose parsing logic 2024-04-16 12:41:21 +02:00
Andras Bacsai
3935a3c885 Refactor ActivityMonitor component to add fullHeight option 2024-04-16 12:41:13 +02:00
Andras Bacsai
15ec39bc56 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 12:24:57 +02:00
Andras Bacsai
5dd2b4c439 Merge pull request #1990 from Dinip/add-unleash-as-a-service
Add Unleash as a service
2024-04-16 12:24:52 +02:00
Andras Bacsai
1ee05dda4b Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:59:01 +02:00
Andras Bacsai
5a86588cb5 Merge pull request #1950 from AmruthPillai/main
Add Reactive Resume as a Service
2024-04-16 11:58:54 +02:00
Andras Bacsai
af3269b362 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:47:22 +02:00
Andras Bacsai
a7a4baa06e Merge pull request #1925 from amerkestijn/main
Added ActivePieces as a service
2024-04-16 11:47:14 +02:00
Andras Bacsai
9a497b9bba Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:03:00 +02:00
Andras Bacsai
d2a1c449da Merge pull request #1907 from hades200082/services/logto
Added logto.io compose
2024-04-16 11:02:48 +02:00
Andras Bacsai
504524ea56 Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:54:28 +02:00
Andras Bacsai
51e22b406c Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:51:49 +02:00
Andras Bacsai
1ec81482cc Merge pull request #1900 from iamEvanYT/cloudflared
Added Cloudflared as a Service
2024-04-16 10:51:40 +02:00
Andras Bacsai
fd610414fd Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:50:28 +02:00
Andras Bacsai
1802d5d5da Add service_type field to Create.php and EditCompose.php 2024-04-16 10:44:23 +02:00
Andras Bacsai
e7e6c76b4e Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:16:28 +02:00
Andras Bacsai
84c7bd63f5 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:12:29 +02:00
Andras Bacsai
f7187bb798 Merge pull request #1899 from kunumigab/main
Added Classicpress as a service
2024-04-16 10:12:20 +02:00
Andras Bacsai
00db5b69ab Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:10:08 +02:00
Andras Bacsai
b16109ca56 Fix repository URL handling in PublicGitRepository.php 2024-04-16 10:09:59 +02:00
Andras Bacsai
a00b5aa136 Update .button styles in app.css 2024-04-16 10:09:56 +02:00
Andras Bacsai
0e43e5d295 Refactor General.php to update application fqdn handling 2024-04-16 09:43:42 +02:00
Andras Bacsai
9f1e2d6192 Update stack-form.blade.php to fix conditional check for fields count 2024-04-16 09:23:31 +02:00
Andras Bacsai
52c794a259 Sort containers when loading and mounting in Logs.php 2024-04-16 09:19:30 +02:00
Andras Bacsai
fcf4c5f328 Merge pull request #1828 from hades200082/services/authentik
Added Authentik identity server as a service
2024-04-16 09:07:13 +02:00
Andras Bacsai
85b33a60b3 feat: can edit file/dir volumes from ui in compose based apps 2024-04-15 19:47:17 +02:00
Andras Bacsai
c99bb4cfd7 Refactor StackForm and parseDockerComposeFile 2024-04-15 16:54:03 +02:00
Andras Bacsai
5b0a942b42 fix: make sure that confs when checking if it is changed sorted 2024-04-15 15:45:50 +02:00
Andras Bacsai
d94c9e522c Update SUPABASE_PUBLIC_URL environment variable in supabase.yaml 2024-04-15 15:25:56 +02:00
Andras Bacsai
8fab15abd3 Fix conditional check for existing tags in tags.blade.php 2024-04-15 15:25:54 +02:00
Andras Bacsai
7717304549 Merge pull request #1987 from Nicell/font-weights
Add missing font weights
2024-04-15 14:47:00 +02:00
Andras Bacsai
b3f1cc3edd Fix unnecessary debug statement in LocalFileVolume.php 2024-04-15 14:46:38 +02:00
Andras Bacsai
16f9c727f2 feat: lazy load stuffs + tell user if compose based deployments have missing envs 2024-04-15 14:23:25 +02:00
Andras Bacsai
007ba5fce9 Update advanced options ui 2024-04-15 13:44:31 +02:00
Andras Bacsai
5b36f07493 feat: literal env variables 2024-04-15 12:46:22 +02:00
Andras Bacsai
cbd2580736 Fix SSH command generation in remoteProcess.php 2024-04-15 11:46:23 +02:00
Andras Bacsai
fe872a59ca fix: helper image only pulled if required, not every 10 mins 2024-04-15 11:11:58 +02:00
Dinip
5a34fe4704 Add unleash service without database 2024-04-14 16:30:29 +01:00
Dinip
2fc3dcce7e Add unleash as a service 2024-04-14 16:21:30 +01:00
Nick Winans
1a932fb749 Add missing font weights 2024-04-14 01:03:58 +02:00
Andras Bacsai
f8a2ee9a8d Update GitHub app styling in project creation form 2024-04-13 21:14:50 +02:00
Andras Bacsai
a972d456ba Update maintenance driver and store in app.php 2024-04-13 21:09:20 +02:00
Andras Bacsai
e9f4c9e687 Update text color in 503 error page 2024-04-13 21:04:38 +02:00
Andras Bacsai
58f7cdc15e Merge pull request #1983 from Emahhh/main
Added a suggestion on an error popup
2024-04-13 16:24:38 +02:00
Andras Bacsai
3f9b0643ee fix: check each required binaries one-by-one 2024-04-13 16:23:44 +02:00
Andras Bacsai
24bd4da94b Update horizon.php and docker-compose.prod.yml 2024-04-13 15:16:57 +02:00
Andras Bacsai
a18b4ffd0f Add allow_promotion_codes option to subscribeStripe method 2024-04-13 14:57:11 +02:00
Eirik Mo
c9e032e350 Update appwrite.yaml 2024-04-13 08:04:49 +02:00
Emanuele B
e1679ad433 Added a suggestion on an error popup 2024-04-13 00:03:36 +02:00
Andras Bacsai
933e395945 feat: dynamic mux time 2024-04-12 23:50:23 +02:00
Andras Bacsai
1f64a00bbf Update version numbers to 4.0.0-beta.259 2024-04-12 23:50:16 +02:00
Andras Bacsai
7eebc0c3a5 Fix bug in login functionality 2024-04-12 23:35:03 +02:00
Andras Bacsai
a2c9dabd77 Ignore service-templates.json in production build workflow 2024-04-12 23:34:44 +02:00
Andras Bacsai
6187c826b4 Merge pull request #1863 from hamanuha/main
Supabase Fixes: Default database name
2024-04-12 23:33:30 +02:00
Andras Bacsai
b8987963b3 Merge pull request #1973 from w3cj/fix-supabase-template
fix #1966: update supabase template
2024-04-12 23:32:24 +02:00
w3cj
0168ef55f0 fix #1966: update supabase template 2024-04-12 07:07:20 -06:00
Amruth Pillai
c3da3499d8 Add "Reactive Resume" to Self-Hostable Services 2024-04-08 16:45:26 +02:00
Eirik Mo
4d8553f959 Merge branch 'next' into main 2024-04-02 22:10:52 +02:00
Eirik Mo
eee1534da7 feat: upgrade Appwrite service template to 1.5 2024-04-02 22:09:39 +02:00
Eirik Mo
38c20f5737 Merge branch 'main' of https://github.com/estubmo/coolify 2024-04-02 21:07:50 +02:00
amerkestijn
ba85c5159d Added the ActivePieces service 2024-03-31 18:44:20 +02:00
amerkestijn
0388c5d5bc Added the ActivePieces service 2024-03-31 18:39:44 +02:00
Evan
3714c80adb Update cloudflared.yaml 2024-03-31 05:53:09 +08:00
Lee Conlin
735c1a23ea Added logto.io compose 2024-03-27 17:17:45 +00:00
iamEvan
88cfc96bd3 Cloudflared Service 2024-03-26 20:34:21 +00:00
Gabriel Peralta
11f389289d Added Classicpress as a service
Wordpress alternative without block editor (Gutenberg), Useful for small sites & lightweight blogs.

PR includes 3 version's with or without database, also logo.svg
2024-03-26 13:58:52 -04:00
Eirik Mo
553c1eacfc Merge branch 'coollabsio:main' into main 2024-03-21 09:39:22 +01:00
Manuel
0032d3cf6c Fix db_name quotes 2024-03-19 11:58:46 +01:00
Manuel
bd81cc1cc4 Use db name parameter for jwt sql 2024-03-19 10:35:50 +01:00
Manuel
bc36fcb722 Supabase Fixes: Default database name 2024-03-18 20:23:50 +01:00
Eirik Mo
11cd553949 Merge branch 'next' into main 2024-03-14 19:21:20 +01:00
Eirik Mo
45a3f82c8d feat: upgrade Appwrite service template to 1.5 2024-03-11 17:57:45 +01:00
Lee Conlin
e9c9a51b8d Added Authentik identity server as a service 2024-03-09 21:50:56 +00:00
216 changed files with 3173 additions and 1133 deletions

1
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1 @@
> Always use `next` branch as destination branch for PRs, not `main`

View File

@@ -3,6 +3,8 @@ name: Production Build (v4)
on: on:
push: push:
branches: ["main"] branches: ["main"]
paths-ignore:
- templates/service-templates.json
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io

View File

@@ -30,5 +30,5 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
Mails are caught by Mailpit: `localhost:8025` Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution ## New Service Contribution
Check out the docs [here](https://coolify.io/docs/resources/services/add-service). Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service).

View File

@@ -99,7 +99,7 @@ class StartClickhouse
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -149,9 +149,9 @@ class StartDatabaseProxy
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false); instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([ instant_remote_process([
"mkdir -p $configuration_dir", "mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile", "echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf", "echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml", "echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
"docker compose --project-directory {$configuration_dir} pull", "docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d", "docker compose --project-directory {$configuration_dir} up --build -d",
], $server); ], $server);

View File

@@ -100,7 +100,7 @@ class StartDragonfly
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -107,7 +107,7 @@ class StartKeydb
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -100,7 +100,7 @@ class StartMariadb
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMariadb
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf; $content = $this->database->mariadb_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
} }
} }

View File

@@ -116,7 +116,7 @@ class StartMongodb
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -184,13 +184,13 @@ class StartMongodb
$filename = 'mongod.conf'; $filename = 'mongod.conf';
$content = $this->database->mongo_conf; $content = $this->database->mongo_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
} }
private function add_default_database() private function add_default_database()
{ {
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});"; $content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d"; $this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
} }
} }

View File

@@ -100,7 +100,7 @@ class StartMysql
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMysql
$filename = 'custom-config.cnf'; $filename = 'custom-config.cnf';
$content = $this->database->mysql_conf; $content = $this->database->mysql_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
} }
} }

View File

@@ -122,7 +122,7 @@ class StartPostgresql
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -197,7 +197,7 @@ class StartPostgresql
$filename = data_get($init_script, 'filename'); $filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content'); $content = data_get($init_script, 'content');
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
} }
} }
@@ -209,6 +209,6 @@ class StartPostgresql
$filename = 'custom-postgres.conf'; $filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf; $content = $this->database->postgres_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
} }
} }

View File

@@ -111,7 +111,7 @@ class StartRedis
} }
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now()); $readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -16,11 +16,11 @@ class CheckConfiguration
return 'OK'; return 'OK';
} }
$proxy_path = $server->proxyPath(); $proxy_path = $server->proxyPath();
$payload = [
$proxy_configuration = instant_remote_process([
"mkdir -p $proxy_path", "mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml", "cat $proxy_path/docker-compose.yml",
], $server, false); ];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) { if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;

View File

@@ -13,8 +13,9 @@ class CheckProxy
if ($server->proxyType() === 'NONE') { if ($server->proxyType() === 'NONE') {
return false; return false;
} }
if (!$server->validateConnection()) { ['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
throw new \Exception("Server Connection Error"); if (!$uptime) {
throw new \Exception($error);
} }
if (!$server->isProxyShouldRun()) { if (!$server->isProxyShouldRun()) {
if ($fromUI) { if ($fromUI) {

View File

@@ -23,7 +23,7 @@ class SaveConfiguration
return instant_remote_process([ return instant_remote_process([
"mkdir -p $proxy_path", "mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", "echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
], $server); ], $server);
} }
} }

View File

@@ -30,16 +30,18 @@ class StartProxy
$server->save(); $server->save();
if ($server->isSwarm()) { if ($server->isSwarm()) {
$commands = $commands->merge([ $commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path", "mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'", "echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'", "echo 'Starting coolify-proxy.'",
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy", "docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'" "echo 'Proxy started successfully.'"
]); ]);
} else { } else {
$caddfile = "import /dynamic/*.caddy"; $caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([ $commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path", "mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile", "echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'", "echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'", "echo 'Pulling docker image.'",

View File

@@ -29,8 +29,9 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2); $config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config); $docker_compose_yml_base64 = base64_encode($config);
$commands = collect([ $commands = collect([
"mkdir -p /tmp/cloudflared && cd /tmp/cloudflared", "mkdir -p /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml", "cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull", "docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1", "docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans", "docker compose up -d --remove-orphans",
@@ -39,6 +40,11 @@ class ConfigureCloudflared
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
throw $e; throw $e;
} finally {
$commands = collect([
"rm -fr /tmp/cloudflared",
]);
instant_remote_process($commands, $server);
} }
} }
} }

View File

@@ -48,20 +48,28 @@ class InstallDocker
if ($supported_os_type->contains('debian')) { if ($supported_os_type->contains('debian')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update -y", "apt-get update -y",
"command -v jq >/dev/null || apt install -y curl wget git jq", "command -v curl >/dev/null || apt install -y curl",
"command -v wget >/dev/null || apt install -y wget",
"command -v git >/dev/null || apt install -y git",
"command -v jq >/dev/null || apt install -y jq",
]); ]);
} else if ($supported_os_type->contains('rhel')) { } else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || dnf install -y curl wget git jq", "command -v curl >/dev/null || dnf install -y curl",
"command -v wget >/dev/null || dnf install -y wget",
"command -v git >/dev/null || dnf install -y git",
"command -v jq >/dev/null || dnf install -y jq",
]); ]);
} else if ($supported_os_type->contains('sles')) { } else if ($supported_os_type->contains('sles')) {
$command = $command->merge([ $command = $command->merge([
"echo 'Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || zypper update -y", "zypper update -y",
"command -v jq >/dev/null || zypper install -y curl wget git jq", "command -v curl >/dev/null || zypper install -y curl",
"command -v wget >/dev/null || zypper install -y wget",
"command -v git >/dev/null || zypper install -y git",
"command -v jq >/dev/null || zypper install -y jq",
]); ]);
} else { } else {
throw new \Exception('Unsupported OS'); throw new \Exception('Unsupported OS');
@@ -70,10 +78,13 @@ class InstallDocker
"echo 'Installing Docker Engine...'", "echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}", "curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json", "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify", "test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify", "echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json", "jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null",
"mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify",
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
"mv /etc/docker/daemon.json.appended /etc/docker/daemon.json",
"echo 'Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true", "systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker", "systemctl restart docker",

View File

@@ -168,10 +168,10 @@ Files:
$command = [ $command = [
"echo 'Saving configuration'", "echo 'Saving configuration'",
"mkdir -p $config_path", "mkdir -p $config_path",
"echo '{$parsers}' | base64 -d > $parsers_config", "echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
"echo '{$config}' | base64 -d > $fluent_bit_config", "echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
"echo '{$compose}' | base64 -d > $compose_path", "echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
"echo '{$readme}' | base64 -d > $readme_path", "echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
"test -f $config_path/.env && rm $config_path/.env", "test -f $config_path/.env && rm $config_path/.env",
]; ];

View File

@@ -62,7 +62,7 @@ class CleanupStuckedResources extends Command
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get(); $keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($keydbs as $keydb) { foreach ($keydbs as $keydb) {
echo "Deleting stuck keydb: {$keydb->name}\n"; echo "Deleting stuck keydb: {$keydb->name}\n";
$redis->forceDelete(); $keydb->forceDelete();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n"; echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
@@ -71,7 +71,7 @@ class CleanupStuckedResources extends Command
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get(); $dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($dragonflies as $dragonfly) { foreach ($dragonflies as $dragonfly) {
echo "Deleting stuck dragonfly: {$dragonfly->name}\n"; echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
$redis->forceDelete(); $dragonfly->forceDelete();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n"; echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
@@ -80,7 +80,7 @@ class CleanupStuckedResources extends Command
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get(); $clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($clickhouses as $clickhouse) { foreach ($clickhouses as $clickhouse) {
echo "Deleting stuck clickhouse: {$clickhouse->name}\n"; echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
$redis->forceDelete(); $clickhouse->forceDelete();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n"; echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";

View File

@@ -20,6 +20,7 @@ class Init extends Command
public function handle() public function handle()
{ {
$this->alive(); $this->alive();
get_public_ips();
$full_cleanup = $this->option('full-cleanup'); $full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments'); $cleanup_deployments = $this->option('cleanup-deployments');
if ($cleanup_deployments) { if ($cleanup_deployments) {
@@ -56,6 +57,7 @@ class Init extends Command
$this->cleanup_stucked_helper_containers(); $this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources'); $this->call('cleanup:stucked-resources');
} }
private function restore_coolify_db_backup() private function restore_coolify_db_backup()
{ {
try { try {

View File

@@ -31,7 +31,7 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule); // $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule); $this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes(); $schedule->command('uploads:clear')->everyTwoMinutes();
} else { } else {

View File

@@ -56,7 +56,7 @@ class Handler extends ExceptionHandler
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (isDev()) { if (isDev()) {
// return; return;
} }
if ($e instanceof RuntimeException) { if ($e instanceof RuntimeException) {
return; return;

View File

@@ -49,6 +49,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $deployment_uuid; private string $deployment_uuid;
private int $pull_request_id; private int $pull_request_id;
private string $commit; private string $commit;
private bool $rollback;
private bool $force_rebuild; private bool $force_rebuild;
private bool $restart_only; private bool $restart_only;
@@ -117,6 +118,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
$this->pull_request_id = $this->application_deployment_queue->pull_request_id; $this->pull_request_id = $this->application_deployment_queue->pull_request_id;
$this->commit = $this->application_deployment_queue->commit; $this->commit = $this->application_deployment_queue->commit;
$this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild; $this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only; $this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server; $this->only_this_server = $this->application_deployment_queue->only_this_server;
@@ -322,7 +324,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}") executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null")
], ],
); );
$this->generate_image_names(); $this->generate_image_names();
@@ -391,7 +393,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]); ]);
$this->save_environment_variables(); $this->save_environment_variables();
// Build new container to limit downtime. // Build new container to limit downtime.
@@ -565,7 +567,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"mkdir -p $this->configuration_dir" "mkdir -p $this->configuration_dir"
], ],
[ [
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName", "echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
], ],
[ [
"echo '{$readme}' > $this->configuration_dir/README.md", "echo '{$readme}' > $this->configuration_dir/README.md",
@@ -708,19 +710,82 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables() private function save_environment_variables()
{ {
$envs = collect([]); $envs = collect([]);
$ports = $this->application->main_port();
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id"; $this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) { foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value); $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$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->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");
}
// Add SOURCE_COMMIT if not exists
if ($this->application->environment_variables_preview->where('key', 'SOURCE_COMMIT')->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
} else { } else {
$this->env_filename = ".env"; $this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) { foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value); $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$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->application->environment_variables->where('key', 'PORT')->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
// 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");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
} }
if ($envs->isEmpty()) { if ($envs->isEmpty()) {
$this->env_filename = null; $this->env_filename = null;
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command( $this->execute_remote_command(
[ [
"command" => "rm -f $this->configuration_dir/{$this->env_filename}", "command" => "rm -f $this->configuration_dir/{$this->env_filename}",
@@ -728,17 +793,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true "ignore_errors" => true
] ]
); );
return; if ($this->use_build_server) {
$this->server = $this->build_server;
}
} else {
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null")
],
);
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
} }
$envs_base64 = base64_encode($envs->implode("\n")); // $this->execute_remote_command([
$this->execute_remote_command( // executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
[ // "hidden" => true,
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env") // "save" => "dotenv"
], // ]);
[ // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
"echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}" // $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
] // $this->execute_remote_command(
); // [
// "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
// ]
// );
// } else {
// $this->execute_remote_command(
// [
// "command" => "rm -f $this->configuration_dir/.env",
// "hidden" => true,
// "ignore_errors" => true
// ]
// );
// }
} }
@@ -973,7 +1072,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh") executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
], ],
[ [
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa") executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null")
], ],
[ [
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa") executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
@@ -993,7 +1092,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
], ],
); );
} }
if ($this->saved_outputs->get('git_commit_sha')) { if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
} }
} }
@@ -1117,15 +1216,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_compose_file() private function generate_compose_file()
{ {
$this->create_workdir(); $this->create_workdir();
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; $ports = $this->application->main_port();
$onlyPort = null; $onlyPort = null;
if (count($ports) > 0) { if (count($ports) > 0) {
$onlyPort = $ports[0]; $onlyPort = $ports[0];
} }
$persistent_storages = $this->generate_local_persistent_volumes(); $persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports); // $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
if (data_get($this->application, 'custom_labels')) { if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels(); $this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
@@ -1159,8 +1258,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview)); $labels = collect(generateLabelsApplication($this->application, $this->preview));
} }
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
// Check for custom HEALTHCHECK // Check for custom HEALTHCHECK
$this->custom_healthcheck_found = false; $this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
@@ -1179,7 +1280,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'image' => $this->production_image_name, 'image' => $this->production_image_name,
'container_name' => $this->container_name, 'container_name' => $this->container_name,
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'environment' => $environment_variables,
'expose' => $ports, 'expose' => $ports,
'networks' => [ 'networks' => [
$this->destination->network, $this->destination->network,
@@ -1200,10 +1300,22 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
] ]
] ]
]; ];
if ($this->env_filename) { // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
$docker_compose['services'][$this->container_name]['env_file'] = [ // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
$this->env_filename // $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
]; // } else {
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
// }
// }
// if ($this->env_filename) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
// } else {
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
// }
// }
if (!is_null($this->env_filename)) {
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
} }
if (!$this->custom_healthcheck_found) { if (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [ $docker_compose['services'][$this->container_name]['healthcheck'] = [
@@ -1357,8 +1469,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose); $this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]);
$this->save_environment_variables();
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -1399,7 +1510,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return $local_persistent_volumes_names; return $local_persistent_volumes_names;
} }
private function generate_environment_variables($ports) /*private function generate_environment_variables($ports)
{ {
$environment_variables = collect(); $environment_variables = collect();
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
@@ -1415,7 +1526,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$real_value = escapeEnvVariables($env->real_value); $real_value = escapeEnvVariables($env->real_value);
} }
$environment_variables->push("$env->key=$real_value"); if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
} }
foreach ($this->application->nixpacks_environment_variables as $env) { foreach ($this->application->nixpacks_environment_variables as $env) {
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -1423,7 +1539,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$real_value = escapeEnvVariables($env->real_value); $real_value = escapeEnvVariables($env->real_value);
} }
$environment_variables->push("$env->key=$real_value"); if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
} }
} else { } else {
foreach ($this->application->runtime_environment_variables_preview as $env) { foreach ($this->application->runtime_environment_variables_preview as $env) {
@@ -1432,7 +1553,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$real_value = escapeEnvVariables($env->real_value); $real_value = escapeEnvVariables($env->real_value);
} }
$environment_variables->push("$env->key=$real_value"); if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
} }
foreach ($this->application->nixpacks_environment_variables_preview as $env) { foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -1440,7 +1566,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$real_value = escapeEnvVariables($env->real_value); $real_value = escapeEnvVariables($env->real_value);
} }
$environment_variables->push("$env->key=$real_value"); if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
} }
} }
// Add PORT if not exists, use the first port as default // Add PORT if not exists, use the first port as default
@@ -1458,8 +1589,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables->push("SOURCE_COMMIT=unknown"); $environment_variables->push("SOURCE_COMMIT=unknown");
} }
} }
ray($environment_variables->all());
return $environment_variables->all(); return $environment_variables->all();
} }*/
private function generate_healthcheck_commands() private function generate_healthcheck_commands()
{ {
@@ -1539,7 +1671,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan); $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
@@ -1560,7 +1692,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
], ],
[ [
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1595,13 +1727,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile") executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")
], ],
[ [
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")
], ],
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
], ],
[ [
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1614,7 +1746,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
], ],
[ [
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1623,7 +1755,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
if ($this->application->build_pack === 'nixpacks') { if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan); $this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) { if ($this->force_rebuild) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
@@ -1644,7 +1776,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
], ],
[ [
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1762,7 +1894,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"), executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
"hidden" => true "hidden" => true
]); ]);
} }

View File

@@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->server->isFunctional()) { if (!$this->server->isFunctional()) {
return 'Server is not ready.'; return 'Server is not ready.';
}; };
$applications = $this->server->applications(); $applications = $this->server->applications();
$skip_these_applications = collect([]); $skip_these_applications = collect([]);
foreach ($applications as $application) { foreach ($applications as $application) {
@@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (is_null($containers)) { if (is_null($containers)) {
return; return;
} }
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) { if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates); $containerReplicates = format_docker_command_output_to_json($containerReplicates);
@@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there. // Notify user that this container should not be there.
} }
} }
} }
if (data_get($container, 'Name') === '/coolify-db') { if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0; $foundDatabases[] = 0;

View File

@@ -2,6 +2,7 @@
namespace App\Jobs; namespace App\Jobs;
use App\Models\Application;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
@@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase $service) public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
{ {
} }
public function handle() public function handle()
{ {
$this->service->getFilesFromServer(isInit: true); $this->resource->getFilesFromServer(isInit: true);
} }
} }

View File

@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId; public $activityId;
public $eventToDispatch = 'activityFinished'; public $eventToDispatch = 'activityFinished';
public $isPollingActive = false; public $isPollingActive = false;
public bool $fullHeight = false;
public bool $showWaiting = false; public bool $showWaiting = false;
protected $activity; protected $activity;

View File

@@ -11,15 +11,17 @@ class NewActivityMonitor extends Component
public ?string $header = null; public ?string $header = null;
public $activityId; public $activityId;
public $eventToDispatch = 'activityFinished'; public $eventToDispatch = 'activityFinished';
public $eventData = null;
public $isPollingActive = false; public $isPollingActive = false;
protected $activity; protected $activity;
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity']; protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished') public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null)
{ {
$this->activityId = $activityId; $this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch; $this->eventToDispatch = $eventToDispatch;
$this->eventData = $eventData;
$this->hydrateActivity(); $this->hydrateActivity();
@@ -55,8 +57,12 @@ class NewActivityMonitor extends Component
} }
return; return;
} }
$this->dispatch($this->eventToDispatch); if (!is_null($this->eventData)) {
ray('Dispatched event: ' . $this->eventToDispatch); $this->dispatch($this->eventToDispatch, $this->eventData);
} else {
$this->dispatch($this->eventToDispatch);
}
ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData);
} }
} }
} }

View File

@@ -27,6 +27,8 @@ class Advanced extends Component
'application.settings.gpu_count' => 'string|required', 'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required', 'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required', 'application.settings.gpu_options' => 'string|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
]; ];
public function mount() { public function mount() {
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled(); $this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
@@ -54,8 +56,14 @@ class Advanced extends Component
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled; $this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
$this->dispatch('resetDefaultLabels', false); $this->dispatch('resetDefaultLabels', false);
} }
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->application->parseCompose();
}
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
} }
public function submit() { public function submit() {
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) { if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
@@ -33,7 +34,8 @@ class General extends Component
public $parsedServiceDomains = []; public $parsedServiceDomains = [];
protected $listeners = [ protected $listeners = [
'resetDefaultLabels' 'resetDefaultLabels',
'configurationChanged' => '$refresh'
]; ];
protected $rules = [ protected $rules = [
'application.name' => 'required', 'application.name' => 'required',
@@ -71,7 +73,6 @@ class General extends Component
'application.post_deployment_command' => 'nullable', 'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable', 'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required', 'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable', 'application.watch_paths' => 'nullable',
]; ];
@@ -107,7 +108,6 @@ class General extends Component
'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.settings.is_static' => 'Is static', 'application.settings.is_static' => 'Is static',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.watch_paths' => 'Watch paths', 'application.watch_paths' => 'Watch paths',
]; ];
@@ -124,7 +124,7 @@ class General extends Component
} }
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
$this->customLabels = $this->application->parseContainerLabels(); $this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') { if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n"); $this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
@@ -156,8 +156,36 @@ class General extends Component
return; return;
} }
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit); ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded'); $this->dispatch('compose_loaded');
$this->dispatch('refresh_storages');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation; $this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation; $this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
@@ -165,7 +193,6 @@ class General extends Component
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {
$this->initLoadingCompose = false; $this->initLoadingCompose = false;
} }
} }
public function generateDomain(string $serviceName) public function generateDomain(string $serviceName)
@@ -184,7 +211,14 @@ class General extends Component
$this->loadComposeFile(); $this->loadComposeFile();
} }
} }
public function updatedApplicationFqdn() { public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->resetDefaultLabels(); $this->resetDefaultLabels();
} }
public function updatedApplicationBuildPack() public function updatedApplicationBuildPack()
@@ -302,11 +336,6 @@ class General extends Component
check_domain_usage(resource: $this->application); check_domain_usage(resource: $this->application);
} }
} }
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->parsedServices = $this->application->parseCompose();
}
} }
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();

View File

@@ -18,7 +18,6 @@ class Rollback extends Component
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
} }
public function rollbackImage($commit) public function rollbackImage($commit)
{ {
$deployment_uuid = new Cuid2(7); $deployment_uuid = new Cuid2(7);
@@ -27,6 +26,7 @@ class Rollback extends Component
application: $this->application, application: $this->application,
deployment_uuid: $deployment_uuid, deployment_uuid: $deployment_uuid,
commit: $commit, commit: $commit,
rollback: true,
force_rebuild: false, force_rebuild: false,
); );
return redirect()->route('project.application.deployment.show', [ return redirect()->route('project.application.deployment.show', [

View File

@@ -12,27 +12,6 @@ class Edit extends Component
'project.name' => 'required|min:3|max:255', 'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255', 'project.description' => 'nullable|string|max:255',
]; ];
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
$this->project->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount() public function mount()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');

View File

@@ -16,28 +16,6 @@ class EnvironmentEdit extends Component
'environment.name' => 'required|min:3|max:255', 'environment.name' => 'required|min:3|max:255',
'environment.description' => 'nullable|min:3|max:255', 'environment.description' => 'nullable|min:3|max:255',
]; ];
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
$this->environment->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();

View File

@@ -89,9 +89,11 @@ class PublicGitRepository extends Component
public function load_branch() public function load_branch()
{ {
try { try {
$this->validate([ if (str($this->repository_url)->startsWith('git@')) {
'repository_url' => 'required|url' $github_instance = str($this->repository_url)->after('git@')->before(':');
]); $repository = str($this->repository_url)->after(':')->before('.git');
$this->repository_url = 'https://' . str($github_instance) . '/' . $repository;
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -40,7 +40,7 @@ class Create extends Component
$database = create_standalone_keydb($environment->id, $destination_uuid); $database = create_standalone_keydb($environment->id, $destination_uuid);
} else if ($type->value() === 'dragonfly') { } else if ($type->value() === 'dragonfly') {
$database = create_standalone_dragonfly($environment->id, $destination_uuid); $database = create_standalone_dragonfly($environment->id, $destination_uuid);
}else if ($type->value() === 'clickhouse') { } else if ($type->value() === 'clickhouse') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid); $database = create_standalone_clickhouse($environment->id, $destination_uuid);
} }
return redirect()->route('project.database.configuration', [ return redirect()->route('project.database.configuration', [
@@ -60,14 +60,19 @@ class Create extends Component
} }
if ($oneClickService) { if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first(); $destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([ $service_payload = [
'name' => "$oneClickServiceName-" . str()->random(10), 'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService), 'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'service_type' => $oneClickServiceName,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,
'destination_id' => $destination->id, 'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(), 'destination_type' => $destination->getMorphClass(),
]); ];
if ($oneClickServiceName === 'cloudflared') {
data_set($service_payload, 'connect_to_docker_network', true);
}
$service = Service::create($service_payload);
$service->name = "$oneClickServiceName-" . $service->uuid; $service->name = "$oneClickServiceName-" . $service->uuid;
$service->save(); $service->save();
if ($oneClickDotEnvs?->count() > 0) { if ($oneClickDotEnvs?->count() > 0) {

View File

@@ -13,11 +13,13 @@ class EditCompose extends Component
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required', 'service.docker_compose' => 'required',
]; ];
public function mount() { public function mount()
{
$this->service = Service::find($this->serviceId); $this->service = Service::find($this->serviceId);
} }
public function saveEditedCompose() { public function saveEditedCompose()
{
$this->dispatch('info', "Saving new docker compose..."); $this->dispatch('info', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw); $this->dispatch('saveCompose', $this->service->docker_compose_raw);
} }

View File

@@ -41,7 +41,7 @@ class EditDomain extends Component
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {
$this->dispatch('generateDockerCompose'); $this->application->service->parse();
$this->dispatch('refresh'); $this->dispatch('refresh');
$this->dispatch('configurationChanged'); $this->dispatch('configurationChanged');
} }

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Models\Application;
use App\Models\LocalFileVolume; use App\Models\LocalFileVolume;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
class FileStorage extends Component class FileStorage extends Component
{ {
public LocalFileVolume $fileStorage; public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource; public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public string $fs_path; public string $fs_path;
public ?string $workdir = null; public ?string $workdir = null;
@@ -33,6 +34,43 @@ class FileStorage extends Component
$this->fs_path = $this->fileStorage->fs_path; $this->fs_path = $this->fileStorage->fs_path;
} }
} }
public function convertToDirectory() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function convertToFile() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function delete() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->delete();
$this->dispatch('success', 'File deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function submit() public function submit()
{ {
$original = $this->fileStorage->getOriginal(); $original = $this->fileStorage->getOriginal();

View File

@@ -3,12 +3,13 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Models\Service; use App\Models\Service;
use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class StackForm extends Component class StackForm extends Component
{ {
public Service $service; public Service $service;
public $fields = []; public Collection $fields;
protected $listeners = ["saveCompose"]; protected $listeners = ["saveCompose"];
public $rules = [ public $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
@@ -20,6 +21,7 @@ class StackForm extends Component
public $validationAttributes = []; public $validationAttributes = [];
public function mount() public function mount()
{ {
$this->fields = collect([]);
$extraFields = $this->service->extraFields(); $extraFields = $this->service->extraFields();
foreach ($extraFields as $serviceName => $fields) { foreach ($extraFields as $serviceName => $fields) {
foreach ($fields as $fieldKey => $field) { foreach ($fields as $fieldKey => $field) {
@@ -27,18 +29,20 @@ class StackForm extends Component
$value = data_get($field, 'value'); $value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable'); $rules = data_get($field, 'rules', 'nullable');
$isPassword = data_get($field, 'isPassword'); $isPassword = data_get($field, 'isPassword');
$this->fields[$key] = [ $this->fields->put($key, [
"serviceName" => $serviceName, "serviceName" => $serviceName,
"key" => $key, "key" => $key,
"name" => $fieldKey, "name" => $fieldKey,
"value" => $value, "value" => $value,
"isPassword" => $isPassword, "isPassword" => $isPassword,
"rules" => $rules "rules" => $rules
]; ]);
$this->rules["fields.$key.value"] = $rules; $this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey; $this->validationAttributes["fields.$key.value"] = $fieldKey;
} }
} }
$this->fields = $this->fields->sortDesc();
} }
public function saveCompose($raw) public function saveCompose($raw)
{ {

View File

@@ -7,12 +7,13 @@ use Livewire\Component;
class Storage extends Component class Storage extends Component
{ {
protected $listeners = ['addNewVolume'];
public $resource; public $resource;
public function getListeners()
public function render()
{ {
return view('livewire.project.service.storage'); return [
'addNewVolume',
'refresh_storages' => '$refresh',
];
} }
public function addNewVolume($data) public function addNewVolume($data)
{ {
@@ -27,9 +28,13 @@ class Storage extends Component
$this->resource->refresh(); $this->resource->refresh();
$this->dispatch('success', 'Storage added successfully'); $this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage'); $this->dispatch('clearAddStorage');
$this->dispatch('refreshStorages'); $this->dispatch('refresh_storages');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function render()
{
return view('livewire.project.service.storage');
}
} }

View File

@@ -7,11 +7,13 @@ use Livewire\Component;
class Add extends Component class Add extends Component
{ {
public $parameters; public $parameters;
public bool $shared = false;
public bool $is_preview = false; public bool $is_preview = false;
public string $key; public string $key;
public ?string $value = null; public ?string $value = null;
public bool $is_build_time = false; public bool $is_build_time = false;
public bool $is_multiline = false; public bool $is_multiline = false;
public bool $is_literal = false;
protected $listeners = ['clearAddEnv' => 'clear']; protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [ protected $rules = [
@@ -19,12 +21,14 @@ class Add extends Component
'value' => 'nullable', 'value' => 'nullable',
'is_build_time' => 'required|boolean', 'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean', 'is_multiline' => 'required|boolean',
'is_literal' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'key' => 'key', 'key' => 'key',
'value' => 'value', 'value' => 'value',
'is_build_time' => 'build', 'is_build_time' => 'build',
'is_multiline' => 'multiline', 'is_multiline' => 'multiline',
'is_literal' => 'literal',
]; ];
public function mount() public function mount()
@@ -47,6 +51,7 @@ class Add extends Component
'value' => $this->value, 'value' => $this->value,
'is_build_time' => $this->is_build_time, 'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline, 'is_multiline' => $this->is_multiline,
'is_literal' => $this->is_literal,
'is_preview' => $this->is_preview, 'is_preview' => $this->is_preview,
]); ]);
$this->clear(); $this->clear();
@@ -58,5 +63,6 @@ class Add extends Component
$this->value = ''; $this->value = '';
$this->is_build_time = false; $this->is_build_time = false;
$this->is_multiline = false; $this->is_multiline = false;
$this->is_literal = false;
} }
} }

View File

@@ -15,8 +15,10 @@ class All extends Component
public ?string $variables = null; public ?string $variables = null;
public ?string $variablesPreview = null; public ?string $variablesPreview = null;
public string $view = 'normal'; public string $view = 'normal';
protected $listeners = ['refreshEnvs', 'saveKey' => 'submit']; protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit',
];
public function mount() public function mount()
{ {
$resourceClass = get_class($this->resource); $resourceClass = get_class($this->resource);
@@ -161,6 +163,7 @@ class All extends Component
$environment->value = $data['value']; $environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time']; $environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline']; $environment->is_multiline = $data['is_multiline'];
$environment->is_literal = $data['is_literal'];
$environment->is_preview = $data['is_preview']; $environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) { switch ($this->resource->type()) {

View File

@@ -16,12 +16,16 @@ class Show extends Component
public bool $isLocked = false; public bool $isLocked = false;
public bool $isSharedVariable = false; public bool $isSharedVariable = false;
public string $type; public string $type;
protected $listeners = [
"compose_loaded" => '$refresh',
];
protected $rules = [ protected $rules = [
'env.key' => 'required|string', 'env.key' => 'required|string',
'env.value' => 'nullable', 'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean', 'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean', 'env.is_multiline' => 'required|boolean',
'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean', 'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable', 'env.real_value' => 'nullable',
]; ];
@@ -30,6 +34,7 @@ class Show extends Component
'env.value' => 'Value', 'env.value' => 'Value',
'env.is_build_time' => 'Build Time', 'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline', 'env.is_multiline' => 'Multiline',
'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once', 'env.is_shown_once' => 'Shown Once',
]; ];

View File

@@ -44,7 +44,7 @@ class Logs extends Component
} else { } else {
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true); $containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
} }
$server->containers = $containers; $server->containers = $containers->sort();
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -94,6 +94,7 @@ class Logs extends Component
$this->servers = $this->servers->push($this->resource->server); $this->servers = $this->servers->push($this->resource->server);
} }
} }
$this->containers = $this->containers->sort();
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -7,10 +7,5 @@ use Livewire\Component;
class All extends Component class All extends Component
{ {
public $resource; public $resource;
protected $listeners = ['refreshStorages']; protected $listeners = ['refresh_storages' => '$refresh'];
public function refreshStorages()
{
$this->resource->refresh();
}
} }

View File

@@ -39,6 +39,6 @@ class Show extends Component
public function delete() public function delete()
{ {
$this->storage->delete(); $this->storage->delete();
$this->dispatch('refreshStorages'); $this->dispatch('refresh_storages');
} }
} }

View File

@@ -76,14 +76,14 @@ class Form extends Component
public function checkLocalhostConnection() public function checkLocalhostConnection()
{ {
$this->submit(); $this->submit();
$uptime = $this->server->validateConnection(); ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) { if ($uptime) {
$this->dispatch('success', 'Server is reachable.'); $this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = true; $this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true; $this->server->settings->is_usable = true;
$this->server->settings->save(); $this->server->settings->save();
} else { } else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.'); $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
return; return;
} }
} }

View File

@@ -23,7 +23,8 @@ class Deploy extends Component
'proxyStatusUpdated', 'proxyStatusUpdated',
'traefikDashboardAvailable', 'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated', 'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy" "checkProxy",
"startProxy"
]; ];
} }

View File

@@ -68,7 +68,7 @@ class NewDynamicConfiguration extends Component
} }
$base64_value = base64_encode($this->value); $base64_value = base64_encode($this->value);
instant_remote_process([ instant_remote_process([
"echo '{$base64_value}' | base64 -d > {$file}", "echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
], $this->server); ], $this->server);
if ($proxy_type === 'CADDY') { if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy(); $this->server->reloadCaddy();

View File

@@ -35,10 +35,11 @@ class ShowPrivateKey extends Component
public function checkConnection() public function checkConnection()
{ {
try { try {
$uptime = $this->server->validateConnection(); ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) { if ($uptime) {
$this->dispatch('success', 'Server is reachable.'); $this->dispatch('success', 'Server is reachable.');
} else { } else {
ray($error);
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.'); $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
return; return;
} }

View File

@@ -11,7 +11,7 @@ class ValidateAndInstall extends Component
{ {
public Server $server; public Server $server;
public int $number_of_tries = 0; public int $number_of_tries = 0;
public int $max_tries = 1; public int $max_tries = 3;
public bool $install = true; public bool $install = true;
public $uptime = null; public $uptime = null;
public $supported_os_type = null; public $supported_os_type = null;
@@ -32,9 +32,8 @@ class ValidateAndInstall extends Component
'refresh' => '$refresh', 'refresh' => '$refresh',
]; ];
public function init(bool $install = true) public function init(int $data = 0)
{ {
$this->install = $install;
$this->uptime = null; $this->uptime = null;
$this->supported_os_type = null; $this->supported_os_type = null;
$this->docker_installed = null; $this->docker_installed = null;
@@ -42,7 +41,7 @@ class ValidateAndInstall extends Component
$this->docker_compose_installed = null; $this->docker_compose_installed = null;
$this->proxy_started = null; $this->proxy_started = null;
$this->error = null; $this->error = null;
$this->number_of_tries = 0; $this->number_of_tries = $data;
if (!$this->ask) { if (!$this->ask) {
$this->dispatch('validateConnection'); $this->dispatch('validateConnection');
} }
@@ -66,16 +65,15 @@ class ValidateAndInstall extends Component
} else { } else {
$this->proxy_started = true; $this->proxy_started = true;
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function validateConnection() public function validateConnection()
{ {
$this->uptime = $this->server->validateConnection(); ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (!$this->uptime) { if (!$this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.'; $this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error;
return; return;
} }
$this->dispatch('validateOS'); $this->dispatch('validateOS');
@@ -99,10 +97,10 @@ class ValidateAndInstall extends Component
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.'; $this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
return; return;
} else { } else {
if ($this->number_of_tries == 0) { if ($this->number_of_tries <= $this->max_tries) {
$activity = $this->server->installDocker(); $activity = $this->server->installDocker();
$this->number_of_tries++; $this->number_of_tries++;
$this->dispatch('newActivityMonitor', $activity->id, 'init'); $this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries);
} }
return; return;
} }

View File

@@ -69,13 +69,14 @@ class Configuration extends Component
} }
$this->validate(); $this->validate();
if ($this->settings->is_dns_validation_enabled) { if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) { if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help."); $this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true; $error_show = true;
} }
} }
check_domain_usage(domain: $this->settings->fqdn); if ($this->settings->fqdn) check_domain_usage(domain: $this->settings->fqdn);
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim(); $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) { $this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower(); return str($dns)->trim()->lower();

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Livewire\SharedVariables\Environment;
use App\Models\Project;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Collection $projects;
public function mount() {
$this->projects = Project::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.shared-variables.environment.index');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Livewire\SharedVariables\Environment;
use App\Models\Application;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
public Application $application;
public $environment;
public array $parameters;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);
$this->environment->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
}
public function render()
{
return view('livewire.shared-variables.environment.show');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Livewire\SharedVariables;
use Livewire\Component;
class Index extends Component
{
public function render()
{
return view('livewire.shared-variables.index');
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Livewire\SharedVariables\Project;
use App\Models\Project;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
public Collection $projects;
public function mount() {
$this->projects = Project::ownedByCurrentTeam()->get();
}
public function render()
{
return view('livewire.shared-variables.project.index');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Livewire\SharedVariables\Project;
use App\Models\Project;
use Livewire\Component;
class Show extends Component
{
public Project $project;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
$this->project->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function mount()
{
$projectUuid = request()->route('project_uuid');
$teamId = currentTeam()->id;
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
if (!$project) {
return redirect()->route('dashboard');
}
$this->project = $project;
}
public function render()
{
return view('livewire.shared-variables.project.show');
}
}

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Livewire; namespace App\Livewire\SharedVariables\Team;
use App\Models\Team; use App\Models\Team;
use Livewire\Component; use Livewire\Component;
class TeamSharedVariablesIndex extends Component class Index extends Component
{ {
public Team $team; public Team $team;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
@@ -21,6 +21,7 @@ class TeamSharedVariablesIndex extends Component
'key' => $data['key'], 'key' => $data['key'],
'value' => $data['value'], 'value' => $data['value'],
'is_multiline' => $data['is_multiline'], 'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'team', 'type' => 'team',
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
@@ -36,6 +37,6 @@ class TeamSharedVariablesIndex extends Component
} }
public function render() public function render()
{ {
return view('livewire.team-shared-variables-index'); return view('livewire.shared-variables.team.index');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Livewire\Team\Storage; namespace App\Livewire\Storage;
use App\Models\S3Storage; use App\Models\S3Storage;
use Livewire\Component; use Livewire\Component;
@@ -65,7 +65,7 @@ class Create extends Component
$this->storage->team_id = currentTeam()->id; $this->storage->team_id = currentTeam()->id;
$this->storage->testConnection(); $this->storage->testConnection();
$this->storage->save(); $this->storage->save();
return redirect()->route('team.storage.show', $this->storage->uuid); return redirect()->route('storage.show', $this->storage->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->dispatch('error', 'Failed to create storage.', $e->getMessage()); $this->dispatch('error', 'Failed to create storage.', $e->getMessage());
// return handleError($e, $this); // return handleError($e, $this);

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Livewire\Team\Storage; namespace App\Livewire\Storage;
use App\Models\S3Storage; use App\Models\S3Storage;
use Livewire\Component; use Livewire\Component;
@@ -43,7 +43,7 @@ class Form extends Component
{ {
try { try {
$this->storage->delete(); $this->storage->delete();
return redirect()->route('team.storage.index'); return redirect()->route('storage.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Livewire\Team\Storage; namespace App\Livewire\Storage;
use App\Models\S3Storage; use App\Models\S3Storage;
use Livewire\Component; use Livewire\Component;
@@ -13,6 +13,6 @@ class Index extends Component
} }
public function render() public function render()
{ {
return view('livewire.team.storage.index'); return view('livewire.storage.index');
} }
} }

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Livewire\Storage;
use App\Models\S3Storage;
use Livewire\Component;
class Show extends Component
{
public $storage = null;
public function mount()
{
$this->storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->first();
if (!$this->storage) {
abort(404);
}
}
public function render()
{
return view('livewire.storage.show');
}
}

View File

@@ -54,6 +54,7 @@ class PricingPlans extends Component
return; return;
} }
$payload = [ $payload = [
'allow_promotion_codes' => true,
'billing_address_collection' => 'required', 'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id, 'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[ 'line_items' => [[

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Livewire\Tags;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
class Deployments extends Component
{
public $deployments_per_tag_per_server = [];
public $resource_ids = [];
public function render()
{
return view('livewire.tags.deployments');
}
public function get_deployments()
{
try {
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $this->resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -20,32 +20,22 @@ class Index extends Component
public $webhook = null; public $webhook = null;
public $deployments_per_tag_per_server = []; public $deployments_per_tag_per_server = [];
public function updatedTag() public function tag_updated()
{ {
if ($this->tag == "") {
return;
}
$tag = $this->tags->where('name', $this->tag)->first(); $tag = $this->tags->where('name', $this->tag)->first();
if (!$tag) {
$this->dispatch('error', "Tag ({$this->tag}) not found.");
$this->tag = "";
return;
}
$this->webhook = generatTagDeployWebhook($tag->name); $this->webhook = generatTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get(); $this->applications = $tag->applications()->get();
$this->services = $tag->services()->get(); $this->services = $tag->services()->get();
$this->get_deployments();
}
public function get_deployments()
{
try {
$resource_ids = $this->applications->pluck('id');
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
} }
public function redeploy_all() public function redeploy_all()
{ {
try { try {
@@ -67,7 +57,7 @@ class Index extends Component
{ {
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name'); $this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
if ($this->tag) { if ($this->tag) {
$this->updatedTag(); $this->tag_updated();
} }
} }
public function render() public function render()

View File

@@ -17,6 +17,6 @@ class Show extends Component
} }
public function render() public function render()
{ {
return view('livewire.team.storage.show'); return view('livewire.storage.show');
} }
} }

View File

@@ -346,6 +346,10 @@ class Application extends BaseModel
} }
return null; return null;
} }
public function main_port()
{
return $this->settings->is_static ? [80] : $this->ports_exposes_array;
}
public function environment_variables(): HasMany public function environment_variables(): HasMany
{ {
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc');
@@ -511,9 +515,9 @@ class Application extends BaseModel
{ {
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build; $newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) { if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else { } else {
$newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables_preview->get('value')->sort());
} }
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
@@ -579,9 +583,9 @@ class Application extends BaseModel
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
$baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid); $baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid);
$commands = collect([]); $commands = collect([]);
$git_clone_command = "git clone -b {$this->git_branch}"; $git_clone_command = "git clone -b \"{$this->git_branch}\"";
if ($only_checkout) { if ($only_checkout) {
$git_clone_command = "git clone --no-checkout -b {$this->git_branch}"; $git_clone_command = "git clone --no-checkout -b \"{$this->git_branch}\"";
} }
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$pr_branch_name = "pr-{$pull_request_id}-coolify"; $pr_branch_name = "pr-{$pull_request_id}-coolify";
@@ -653,13 +657,13 @@ class Application extends BaseModel
if ($exec_in_docker) { if ($exec_in_docker) {
$commands = collect([ $commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"), executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
]); ]);
} else { } else {
$commands = collect([ $commands = collect([
"mkdir -p /root/.ssh", "mkdir -p /root/.ssh",
"echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa", "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
"chmod 600 /root/.ssh/id_rsa", "chmod 600 /root/.ssh/id_rsa",
]); ]);
} }
@@ -954,4 +958,9 @@ class Application extends BaseModel
}); });
return $matches->count() > 0; return $matches->count() > 0;
} }
public function getFilesFromServer(bool $isInit = false)
{
getFilesystemVolumesFromServer($this, $isInit);
}
} }

View File

@@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
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\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class EnvironmentVariable extends Model class EnvironmentVariable extends Model
{ {
@@ -81,6 +82,44 @@ class EnvironmentVariable extends Model
} }
); );
} }
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->application_id) {
return true;
}
$found_in_compose = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
}
return $found_in_compose;
}
);
}
protected function isShared(): Attribute protected function isShared(): Attribute
{ {
return Attribute::make( return Attribute::make(

View File

@@ -20,6 +20,26 @@ class LocalFileVolume extends BaseModel
{ {
return $this->morphTo('resource'); return $this->morphTo('resource');
} }
public function deleteStorageOnServer()
{
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([
"cd $workdir"
]);
$fs_path = data_get($this, 'fs_path');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
$commands->push("rm -rf $fs_path");
}
ray($commands);
return instant_remote_process($commands, $server);
}
public function saveStorageOnServer() public function saveStorageOnServer()
{ {
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
@@ -63,8 +83,7 @@ class LocalFileVolume extends BaseModel
$content = base64_encode($content); $content = base64_encode($content);
$chmod = $fileVolume->chmod; $chmod = $fileVolume->chmod;
$chown = $fileVolume->chown; $chown = $fileVolume->chown;
ray($content, $path, $chmod, $chown); $commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
$commands->push("echo '$content' | base64 -d > $path");
$commands->push("chmod +x $path"); $commands->push("chmod +x $path");
if ($chown) { if ($chown) {
$commands->push("chown $chown $path"); $commands->push("chown $chown $path");
@@ -72,7 +91,6 @@ class LocalFileVolume extends BaseModel
if ($chmod) { if ($chmod) {
$commands->push("chmod $chmod $path"); $commands->push("chmod $chmod $path");
} }
} }
} else if ($isDir == 'NOK' && $fileVolume->is_directory) { } else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true"); $commands->push("mkdir -p $path > /dev/null 2>&1 || true");

View File

@@ -48,7 +48,7 @@ class S3Storage extends BaseModel
if ($this->unusable_email_sent === false && is_transactional_emails_active()) { if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject('Coolify: S3 Storage Connection Error'); $mail->subject('Coolify: S3 Storage Connection Error');
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storage.show', ['storage_uuid' => $this->uuid])]); $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]);
$users = collect([]); $users = collect([]);
$members = $this->team->members()->get(); $members = $this->team->members()->get();
foreach ($members as $user) { foreach ($members as $user) {

View File

@@ -141,7 +141,7 @@ respond 404
$base64 = base64_encode($conf); $base64 = base64_encode($conf);
instant_remote_process([ instant_remote_process([
"mkdir -p $dynamic_conf_path", "mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file", "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this); ], $this);
$this->reloadCaddy(); $this->reloadCaddy();
return; return;
@@ -223,7 +223,7 @@ respond 404
instant_remote_process([ instant_remote_process([
"mkdir -p $dynamic_conf_path", "mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file", "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this); ], $this);
if (config('app.env') == 'local') { if (config('app.env') == 'local') {
@@ -349,7 +349,7 @@ respond 404
$base64 = base64_encode($yaml); $base64 = base64_encode($yaml);
instant_remote_process([ instant_remote_process([
"mkdir -p $dynamic_config_path", "mkdir -p $dynamic_config_path",
"echo '$base64' | base64 -d > $file", "echo '$base64' | base64 -d | tee $file > /dev/null",
], $this); ], $this);
if (config('app.env') == 'local') { if (config('app.env') == 'local') {
@@ -376,7 +376,7 @@ $schema://$host {
}"; }";
$base64 = base64_encode($caddy_file); $base64 = base64_encode($caddy_file);
instant_remote_process([ instant_remote_process([
"echo '$base64' | base64 -d > $file", "echo '$base64' | base64 -d | tee $file > /dev/null",
], $this); ], $this);
$this->reloadCaddy(); $this->reloadCaddy();
} }
@@ -481,8 +481,8 @@ $schema://$host {
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber); // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax); // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
$result = $this->validateConnection(); ['uptime' => $uptime] = $this->validateConnection();
if ($result) { if ($uptime) {
if ($this->unreachable_notification_sent === true) { if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]); $this->update(['unreachable_notification_sent' => false]);
} }
@@ -551,7 +551,7 @@ $schema://$host {
public function loadUnmanagedContainers() public function loadUnmanagedContainers()
{ {
if ($this->isFunctional()) { if ($this->isFunctional()) {
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
$containers = $containers->map(function ($container) { $containers = $containers->map(function ($container) {
$labels = data_get($container, 'Labels'); $labels = data_get($container, 'Labels');
@@ -748,34 +748,31 @@ $schema://$host {
$server = Server::find($this->id); $server = Server::find($this->id);
if (!$server) { if (!$server) {
return false; return ['uptime' => false, 'error' => 'Server not found.'];
} }
if ($server->skipServer()) { if ($server->skipServer()) {
return false; return ['uptime' => false, 'error' => 'Server skipped.'];
} }
// EC2 does not have `uptime` command, lol try {
// EC2 does not have `uptime` command, lol
$uptime = instant_remote_process(['ls /'], $server, false); instant_remote_process(['ls /'], $server);
if (!$uptime) {
$server->settings()->update([
'is_reachable' => false,
]);
return false;
} else {
$server->settings()->update([ $server->settings()->update([
'is_reachable' => true, 'is_reachable' => true,
]); ]);
$server->update([ $server->update([
'unreachable_count' => 0, 'unreachable_count' => 0,
]); ]);
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
$server->settings()->update([
'is_reachable' => false,
]);
return ['uptime' => false, 'error' => $e->getMessage()];
} }
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return true;
} }
public function installDocker() public function installDocker()
{ {
@@ -784,7 +781,7 @@ $schema://$host {
} }
public function validateDockerEngine($throwError = false) public function validateDockerEngine($throwError = false)
{ {
$dockerBinary = instant_remote_process(["command -v docker"], $this, false); $dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true);
if (is_null($dockerBinary)) { if (is_null($dockerBinary)) {
$this->settings->is_usable = false; $this->settings->is_usable = false;
$this->settings->save(); $this->settings->save();
@@ -861,4 +858,11 @@ $schema://$host {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
} }
} }
public function isNonRoot()
{
if ($this->user instanceof Stringable) {
return $this->user->value() !== 'root';
}
return $this->user !== 'root';
}
} }

View File

@@ -14,20 +14,20 @@ class Service extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$domains = $this->applications()->get()->pluck('fqdn')->toArray(); $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
$domains = implode(',', $domains); $domains = implode(',', $domains);
$applicationImages = $this->applications()->get()->pluck('image'); $applicationImages = $this->applications()->get()->pluck('image')->sort();
$databaseImages = $this->databases()->get()->pluck('image'); $databaseImages = $this->databases()->get()->pluck('image')->sort();
$images = $applicationImages->merge($databaseImages); $images = $applicationImages->merge($databaseImages);
$images = implode(',', $images->toArray()); $images = implode(',', $images->toArray());
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten(); $applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten(); $databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at'); $storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
$newConfigHash = $images . $domains . $images . $storages; $newConfigHash = $images . $domains . $images . $storages;
$newConfigHash .= json_encode($this->environment_variables()->get('value')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -150,6 +150,76 @@ class Service extends BaseModel
foreach ($applications as $application) { foreach ($applications as $application) {
$image = str($application->image)->before(':')->value(); $image = str($application->image)->before(':')->value();
switch ($image) { switch ($image) {
case str($image)?->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'TOLGEE_AUTHENTICATION_INITIAL_USERNAME',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_TOLGEE',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Tolgee', $data);
break;
case str($image)?->contains('logto'):
$data = collect([]);
$logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
$logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
if ($logto_endpoint) {
$data = $data->merge([
'Endpoint' => [
'key' => data_get($logto_endpoint, 'key'),
'value' => data_get($logto_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
if ($logto_admin_endpoint) {
$data = $data->merge([
'Admin Endpoint' => [
'key' => data_get($logto_admin_endpoint, 'key'),
'value' => data_get($logto_admin_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
$fields->put('Logto', $data);
break;
case str($image)?->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'SERVICE_USER_UNLEASH',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_UNLEASH',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Unleash', $data);
break;
case str($image)?->contains('grafana'): case str($image)?->contains('grafana'):
$data = collect([]); $data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
@@ -618,7 +688,7 @@ class Service extends BaseModel
$commands[] = "cd $workdir"; $commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($this->docker_compose); $docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml"; $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$envs = $this->environment_variables()->get(); $envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true"; $commands[] = "rm -f .env || true";
foreach ($envs as $env) { foreach ($envs as $env) {

View File

@@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model class SharedEnvironmentVariable extends Model

View File

@@ -45,7 +45,7 @@ class StandaloneClickhouse extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings; $newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -207,4 +207,7 @@ class StandaloneClickhouse extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->clickhouse_db;
}
} }

View File

@@ -44,7 +44,7 @@ class StandaloneDragonfly extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings; $newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -167,9 +167,9 @@ class StandaloneDragonfly extends BaseModel
public function get_db_url(bool $useInternal = false): string public function get_db_url(bool $useInternal = false): string
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
} else { } else {
return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0"; return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
} }
} }
@@ -207,4 +207,7 @@ class StandaloneDragonfly extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@@ -44,7 +44,7 @@ class StandaloneKeydb extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf; $newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -208,5 +208,7 @@ class StandaloneKeydb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@@ -46,7 +46,7 @@ class StandaloneMariadb extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf; $newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -208,4 +208,7 @@ class StandaloneMariadb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->mariadb_database;
}
} }

View File

@@ -49,7 +49,7 @@ class StandaloneMongodb extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf; $newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -223,4 +223,7 @@ class StandaloneMongodb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return null;
}
} }

View File

@@ -46,7 +46,7 @@ class StandaloneMysql extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf; $newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -209,4 +209,7 @@ class StandaloneMysql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->mysql_database;
}
} }

View File

@@ -58,7 +58,7 @@ class StandalonePostgresql extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method; $newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -208,4 +208,7 @@ class StandalonePostgresql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return $this->postgres_db;
}
} }

View File

@@ -41,7 +41,7 @@ class StandaloneRedis extends BaseModel
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf; $newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at')); $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash); $newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash'); $oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) { if ($oldConfigHash === null) {
@@ -204,4 +204,7 @@ class StandaloneRedis extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function database_name() {
return '0';
}
} }

View File

@@ -17,11 +17,13 @@ class BackupFailed extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public string $name; public string $name;
public ?string $database_name = null;
public string $frequency; public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output) public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
{ {
$this->name = $database->name; $this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency; $this->frequency = $backup->frequency;
} }
@@ -36,6 +38,7 @@ class BackupFailed extends Notification implements ShouldQueue
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [ $mail->view('emails.backup-failed', [
'name' => $this->name, 'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency, 'frequency' => $this->frequency,
'output' => $this->output, 'output' => $this->output,
]); ]);
@@ -44,11 +47,11 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return [ return [
"message" => $message, "message" => $message,
]; ];

View File

@@ -14,11 +14,13 @@ class BackupSuccess extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public string $name; public string $name;
public ?string $database_name = null;
public string $frequency; public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database) public function __construct(ScheduledDatabaseBackup $backup, public $database)
{ {
$this->name = $database->name; $this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency; $this->frequency = $backup->frequency;
} }
@@ -33,6 +35,7 @@ class BackupSuccess extends Notification implements ShouldQueue
$mail->subject("Coolify: Backup successfully done for {$this->database->name}"); $mail->subject("Coolify: Backup successfully done for {$this->database->name}");
$mail->view('emails.backup-success', [ $mail->view('emails.backup-success', [
'name' => $this->name, 'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency, 'frequency' => $this->frequency,
]); ]);
return $mail; return $mail;
@@ -40,11 +43,11 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; $message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
return [ return [
"message" => $message, "message" => $message,
]; ];

View File

@@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabledNotification::class, MaintenanceModeDisabledNotification::class,
], ],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', \SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
], ],
ProxyStarted::class => [ ProxyStarted::class => [
ProxyStartedNotification::class, ProxyStartedNotification::class,
], ],
]; ];

View File

@@ -34,7 +34,13 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false); $ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true); $append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save'); $this->save = data_get($single_command, 'save');
if ($this->server->isNonRoot()) {
if (str($command)->startsWith('docker exec')) {
$command = str($command)->replace('docker exec', 'sudo docker exec');
} else {
$command = parseLineForSudo($command, $this->server);
}
}
$remote_command = generateSshCommand($this->server, $command); $remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) { $process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$output = Str::of($output)->trim(); $output = Str::of($output)->trim();

View File

@@ -0,0 +1,38 @@
<?php
namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
class Datalist extends Component
{
/**
* Create a new component instance.
*/
public function __construct(
public ?string $id = null,
public ?string $name = null,
public ?string $label = null,
public ?string $helper = null,
public bool $required = false,
public string $defaultClass = "input"
) {
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
if (is_null($this->id)) $this->id = new Cuid2(7);
if (is_null($this->name)) $this->name = $this->id;
$this->label = Str::title($this->label);
return view('components.forms.datalist');
}
}

View File

@@ -23,10 +23,11 @@ class Textarea extends Component
public bool $disabled = false, public bool $disabled = false,
public bool $readonly = false, public bool $readonly = false,
public bool $allowTab = false, public bool $allowTab = false,
public bool $spellcheck = false,
public ?string $helper = null, public ?string $helper = null,
public bool $realtimeValidation = false, public bool $realtimeValidation = false,
public bool $allowToPeak = true, public bool $allowToPeak = true,
public string $defaultClass = "input scrollbar", public string $defaultClass = "input scrollbar font-mono",
public string $defaultClassInput = "input" public string $defaultClassInput = "input"
) { ) {
// //

View File

@@ -18,7 +18,10 @@ class Links extends Component
$service->applications()->get()->map(function ($application) { $service->applications()->get()->map(function ($application) {
$type = $application->serviceType(); $type = $application->serviceType();
if ($type) { if ($type) {
$links = generateServiceSpecificFqdns($application, false); $links = generateServiceSpecificFqdns($application);
$links = $links->map(function ($link) {
return getFqdnWithoutPort($link);
});
$this->links = $this->links->merge($links); $this->links = $this->links->merge($links);
} else { } else {
if ($application->fqdn) { if ($application->fqdn) {

View File

@@ -9,7 +9,7 @@ use App\Models\StandaloneDocker;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Spatie\Url\Url; use Spatie\Url\Url;
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false) function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)
{ {
$application_id = $application->id; $application_id = $application->id;
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
@@ -38,6 +38,7 @@ function queue_application_deployment(Application $application, string $deployme
'is_webhook' => $is_webhook, 'is_webhook' => $is_webhook,
'restart_only' => $restart_only, 'restart_only' => $restart_only,
'commit' => $commit, 'commit' => $commit,
'rollback' => $rollback,
'git_type' => $git_type, 'git_type' => $git_type,
'only_this_server' => $only_this_server 'only_this_server' => $only_this_server
]); ]);

View File

@@ -32,6 +32,7 @@ const DATABASE_DOCKER_IMAGES = [
]; ];
const SPECIFIC_SERVICES = [ const SPECIFIC_SERVICES = [
'quay.io/minio/minio', 'quay.io/minio/minio',
'svhd/logto'
]; ];
// Based on /etc/os-release // Based on /etc/os-release

View File

@@ -169,7 +169,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
} }
return $labels; return $labels;
} }
function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $forTraefik = false) function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{ {
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') { if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
$uuid = $resource->uuid; $uuid = $resource->uuid;
@@ -182,6 +182,9 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
$environment_variables = $resource->environment_variables; $environment_variables = $resource->environment_variables;
$type = $resource->serviceType(); $type = $resource->serviceType();
} }
if (is_null($server) || is_null($type)) {
return collect([]);
}
$variables = collect($environment_variables); $variables = collect($environment_variables);
$payload = collect([]); $payload = collect([]);
switch ($type) { switch ($type) {
@@ -201,17 +204,31 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
"value" => generateFqdn($server, 'minio-' . $uuid) "value" => generateFqdn($server, 'minio-' . $uuid)
]); ]);
} }
if ($forTraefik) { $payload = collect([
$payload = collect([ $MINIO_BROWSER_REDIRECT_URL->value . ':9001',
$MINIO_BROWSER_REDIRECT_URL->value . ':9001', $MINIO_SERVER_URL->value . ':9000',
$MINIO_SERVER_URL->value . ':9000', ]);
]); break;
} else { case $type?->contains('logto'):
$payload = collect([ $LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
$MINIO_BROWSER_REDIRECT_URL->value, $LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
$MINIO_SERVER_URL->value, if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) {
return $payload;
}
if (is_null($LOGTO_ENDPOINT?->value)) {
$LOGTO_ENDPOINT?->update([
"value" => generateFqdn($server, 'logto-' . $uuid)
]); ]);
} }
if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) {
$LOGTO_ADMIN_ENDPOINT?->update([
"value" => generateFqdn($server, 'logto-admin-' . $uuid)
]);
}
$payload = collect([
$LOGTO_ENDPOINT->value . ':3001',
$LOGTO_ADMIN_ENDPOINT->value . ':3002',
]);
break; break;
} }
return $payload; return $payload;
@@ -565,7 +582,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
$server = Server::findOrFail($server_id); $server = Server::findOrFail($server_id);
$base64_compose = base64_encode($compose); $base64_compose = base64_encode($compose);
$output = instant_remote_process([ $output = instant_remote_process([
"echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml", "echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null",
"docker compose -f /tmp/{$uuid}.yml config", "docker compose -f /tmp/{$uuid}.yml config",
], $server); ], $server);
ray($output); ray($output);
@@ -586,3 +603,9 @@ function escapeEnvVariables($value)
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'"); $replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'");
return str_replace($search, $replace, $value); return str_replace($search, $replace, $value);
} }
function escapeDollarSign($value)
{
$search = array('$');
$replace = array('$$');
return str_replace($search, $replace, $value);
}

View File

@@ -32,6 +32,9 @@ function remote_process(
if ($command instanceof Collection) { if ($command instanceof Collection) {
$command = $command->toArray(); $command = $command->toArray();
} }
if ($server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
$command_string = implode("\n", $command); $command_string = implode("\n", $command);
if (auth()->user()) { if (auth()->user()) {
$teams = auth()->user()->teams->pluck('id'); $teams = auth()->user()->teams->pluck('id');
@@ -133,17 +136,17 @@ function generateSshCommand(Server $server, string $command)
$timeout = config('constants.ssh.command_timeout'); $timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout'); $connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval'); $serverInterval = config('constants.ssh.server_interval');
$muxPersistTime = config('constants.ssh.mux_persist_time');
$ssh_command = "timeout $timeout ssh "; $ssh_command = "timeout $timeout ssh ";
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) { if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
} }
if (data_get($server, 'settings.is_cloudflare_tunnel')) { if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
} }
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command); $delimiter = Hash::make($command);
$command = str_replace($delimiter, '', $command); $command = str_replace($delimiter, '', $command);
$ssh_command .= "-i {$privateKeyLocation} " $ssh_command .= "-i {$privateKeyLocation} "
@@ -159,17 +162,19 @@ function generateSshCommand(Server $server, string $command)
. $command . PHP_EOL . $command . PHP_EOL
. $delimiter; . $delimiter;
// ray($ssh_command); // ray($ssh_command);
// ray($delimiter);
return $ssh_command; return $ssh_command;
} }
function instant_remote_process(Collection|array $command, Server $server, $throwError = true) function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
{ {
$timeout = config('constants.ssh.command_timeout'); $timeout = config('constants.ssh.command_timeout');
if ($command instanceof Collection) { if ($command instanceof Collection) {
$command = $command->toArray(); $command = $command->toArray();
} }
if ($server->isNonRoot() && !$no_sudo) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
$command_string = implode("\n", $command); $command_string = implode("\n", $command);
$ssh_command = generateSshCommand($server, $command_string); $ssh_command = generateSshCommand($server, $command_string, $no_sudo);
$process = Process::timeout($timeout)->run($ssh_command); $process = Process::timeout($timeout)->run($ssh_command);
$output = trim($process->output()); $output = trim($process->output());
$exitCode = $process->exitCode(); $exitCode = $process->exitCode();

View File

@@ -1,7 +1,7 @@
<?php <?php
use App\Models\Application;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -21,16 +21,20 @@ function replaceVariables($variable)
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', ''); return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
} }
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false) function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
{ {
// TODO: make this async
try { try {
$workdir = $oneService->service->workdir(); if ($oneService->getMorphClass() === 'App\Models\Application') {
$server = $oneService->service->server; $workdir = $oneService->workdir();
$server = $oneService->destination->server;
} else{
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
}
$fileVolumes = $oneService->fileStorages()->get(); $fileVolumes = $oneService->fileStorages()->get();
$commands = collect([ $commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true", "mkdir -p $workdir > /dev/null 2>&1 || true",
"cd " "cd $workdir"
]); ]);
instant_remote_process($commands, $server); instant_remote_process($commands, $server);
foreach ($fileVolumes as $fileVolume) { foreach ($fileVolumes as $fileVolume) {
@@ -67,7 +71,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
$dir = Str::of($fileLocation)->dirname(); $dir = Str::of($fileLocation)->dirname();
instant_remote_process([ instant_remote_process([
"mkdir -p $dir", "mkdir -p $dir",
"echo '$content' | base64 -d > $fileLocation" "echo '$content' | base64 -d | tee $fileLocation"
], $server); ], $server);
} else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) { } else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
$fileVolume->content = null; $fileVolume->content = null;

View File

@@ -29,10 +29,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Process\Pool;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -656,6 +659,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (str(data_get($service, 'image'))->contains('glitchtip')) { if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip'; $tempServiceName = 'glitchtip';
} }
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName); $serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port'); $predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') { if ($serviceName === 'plausible') {
@@ -978,12 +984,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
// Caddy needs exact port in some cases. // Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) { if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
}
if ($resource->server->proxyType() === 'CADDY') { if ($resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([ $env = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
if ($env) { if ($env) {
$env_url = Url::fromString($savedService->fqdn); $env_url = Url::fromString($savedService->fqdn);
$env_port = $env_url->getPort(); $env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) { if ($env_port !== $predefinedPort) {
@@ -1045,6 +1056,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
// Caddy needs exact port in some cases. // Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') { if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
}
$env = EnvironmentVariable::where([ $env = EnvironmentVariable::where([
'key' => $key, 'key' => $key,
'service_id' => $resource->id, 'service_id' => $resource->id,
@@ -1109,10 +1124,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} }
} }
// Add labels to the service // Add labels to the service
if ($savedService->serviceType()) { if ($savedService->serviceType()) {
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true); $fqdns = generateServiceSpecificFqdns($savedService);
} else { } else {
$fqdns = collect(data_get($savedService, 'fqdns'))->filter(); $fqdns = collect(data_get($savedService, 'fqdns'))->filter();
} }
@@ -1254,6 +1268,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels->push("$removedLabelName=$removedLabel"); $serviceLabels->push("$removedLabelName=$removedLabel");
} }
} }
if ($serviceLabels->count() > 0) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$baseName = generateApplicationContainerName($resource, $pull_request_id); $baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName"; $containerName = "$serviceName-$baseName";
if (count($serviceVolumes) > 0) { if (count($serviceVolumes) > 0) {
@@ -1412,6 +1431,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
foreach ($definedNetwork as $key => $network) { foreach ($definedNetwork as $key => $network) {
$networks->put($network, null); $networks->put($network, null);
} }
if (data_get($resource, 'settings.connect_to_docker_network')) {
$network = $resource->destination->network;
$networks->put($network, null);
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
}
data_set($service, 'networks', $networks->toArray()); data_set($service, 'networks', $networks->toArray());
// Get variables from the service // Get variables from the service
foreach ($serviceVariables as $variableName => $variable) { foreach ($serviceVariables as $variableName => $variable) {
@@ -1482,7 +1509,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id, 'application_id' => $resource->id,
])->first(); ])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
ray($command, $generatedValue);
if (!is_null($command)) { if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) { if (Str::lower($forService) === $serviceName) {
@@ -1567,14 +1593,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
// Add labels to the service // Add labels to the service
if ($resource->serviceType()) { if ($resource->serviceType()) {
$fqdns = generateServiceSpecificFqdns($resource, forTraefik: true); $fqdns = generateServiceSpecificFqdns($resource);
} else { } else {
$domains = collect(json_decode($resource->docker_compose_domains)) ?? []; $domains = collect(json_decode($resource->docker_compose_domains)) ?? [];
if ($domains) { if ($domains) {
$fqdns = data_get($domains, "$serviceName.domain"); $fqdns = data_get($domains, "$serviceName.domain");
if ($fqdns) { if ($fqdns) {
$fqdns = str($fqdns)->explode(','); $fqdns = str($fqdns)->explode(',');
$uuid = new Cuid2(7);
if ($pull_request_id !== 0) { if ($pull_request_id !== 0) {
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) { $fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id); $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
@@ -1593,13 +1618,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}); });
} }
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $uuid, uuid: $resource->uuid,
domains: $fqdns, domains: $fqdns,
serviceLabels: $serviceLabels serviceLabels: $serviceLabels
)); ));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $resource->destination->network, network: $resource->destination->network,
uuid: $uuid, uuid: $resource->uuid,
domains: $fqdns, domains: $fqdns,
serviceLabels: $serviceLabels serviceLabels: $serviceLabels
)); ));
@@ -1939,3 +1964,91 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
} }
} }
} }
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (!str($line)->startsWith('cd') && !str($line)->startsWith('command') && !str($line)->startsWith('echo') && !str($line)->startsWith('true')) {
return "sudo $line";
}
return $line;
});
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
}
return $line;
});
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
$line = $line->replace('$(', '$(sudo ');
}
if (str($line)->contains('||')) {
$line = $line->replace('||', '|| sudo');
}
if (str($line)->contains('&&')) {
$line = $line->replace('&&', '&& sudo');
}
if (str($line)->contains(' | ')) {
$line = $line->replace(' | ', ' | sudo ');
}
return $line->value();
});
return $commands->toArray();
}
function parseLineForSudo(string $command, Server $server): string
{
if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) {
$command = "sudo $command";
}
if (Str::startsWith($command, 'sudo mkdir -p')) {
$command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p');
}
if (str($command)->contains('$(') || str($command)->contains('`')) {
$command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
}
if (str($command)->contains('||')) {
$command = str($command)->replace('||', '|| sudo ')->value();
}
if (str($command)->contains('&&')) {
$command = str($command)->replace('&&', '&& sudo ')->value();
}
return $command;
}
function get_public_ips()
{
try {
echo "Refreshing public ips!\n";
$settings = InstanceSettings::get();
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
});
$ipv4 = $first->output();
if ($ipv4) {
$ipv4 = trim($ipv4);
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
if ($validate_ipv4 == false) {
echo "Invalid ipv4: $ipv4\n";
return;
}
$settings->update(['public_ipv4' => $ipv4]);
}
$ipv6 = $second->output();
if ($ipv6) {
$ipv6 = trim($ipv6);
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
if ($validate_ipv6 == false) {
echo "Invalid ipv6: $ipv6\n";
return;
}
$settings->update(['public_ipv6' => $ipv6]);
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
}

View File

@@ -141,8 +141,8 @@ return [
*/ */
'maintenance' => [ 'maintenance' => [
'driver' => 'file', 'driver' => 'cache',
// 'store' => 'redis', 'store' => 'redis',
], ],
/* /*

View File

@@ -5,6 +5,7 @@ return [
'contact' => 'https://coolify.io/docs/contact', 'contact' => 'https://coolify.io/docs/contact',
], ],
'ssh' => [ 'ssh' => [
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', "1m"),
'connection_timeout' => 10, 'connection_timeout' => 10,
'server_interval' => 20, 'server_interval' => 20,
'command_timeout' => 7200, 'command_timeout' => 7200,

View File

@@ -183,9 +183,7 @@ return [
's6' => [ 's6' => [
'connection' => 'redis', 'connection' => 'redis',
'queue' => ['default'], 'queue' => ['default'],
'balance' => 'auto', 'balance' => env('HORIZON_BALANCE', 'auto'),
// 'autoScalingStrategy' => 'time',
// 'maxProcesses' => 1,
'maxTime' => 0, 'maxTime' => 0,
'maxJobs' => 0, 'maxJobs' => 0,
'memory' => 128, 'memory' => 128,

View File

@@ -146,4 +146,5 @@ return [
*/ */
'pagination_theme' => 'tailwind', 'pagination_theme' => 'tailwind',
'lazy_placeholder' => 'components.page-loading',
]; ];

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.258', 'release' => '4.0.0-beta.270',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.258'; return '4.0.0-beta.270';

Some files were not shown because too many files have changed in this diff Show More