Compare commits

...

646 Commits

Author SHA1 Message Date
Andras Bacsai
b7b2ecad59 Merge pull request #1888 from coollabsio/next
v4.0.0-beta.244
2024-03-26 10:52:24 +01:00
Andras Bacsai
dcaa2f4168 Refactor error pages 2024-03-26 10:44:52 +01:00
Andras Bacsai
7c7f54d224 Refactor modal input component transitions 2024-03-26 10:29:19 +01:00
Andras Bacsai
b942f8c726 ui ui ui 2024-03-26 10:24:53 +01:00
Andras Bacsai
f661f23ee5 Update background color and text color in app.css 2024-03-25 19:16:39 +01:00
Andras Bacsai
5a631df2a2 uiuiuiui 2024-03-25 19:07:59 +01:00
Andras Bacsai
fc9bb7dac6 Delete user and send email snippets 2024-03-25 16:52:34 +01:00
Andras Bacsai
0a82dc2e8e Remove unused Livewire components 2024-03-25 16:45:34 +01:00
Andras Bacsai
c5eff85c28 Update version numbers and fix UI styling 2024-03-25 16:42:41 +01:00
Andras Bacsai
d1627276a6 Merge pull request #1887 from coollabsio/next
v4.0.0-beta.243
2024-03-25 14:54:35 +01:00
Andras Bacsai
2be2a2621e Update conditional statements in app.blade.php and navbar.blade.php 2024-03-25 14:44:31 +01:00
Andras Bacsai
995c303f27 Refactor Livewire/Boarding/Index.php and resources/views/livewire/boarding/index.blade.php 2024-03-25 14:34:11 +01:00
Andras Bacsai
9c03525369 Remove commented out code in two-factor-challenge.blade.php 2024-03-25 14:29:11 +01:00
Andras Bacsai
afe0673fd1 Commented out x-version component in confirm-password and forgot-password views 2024-03-25 14:24:09 +01:00
Andras Bacsai
37333f7fbe fix: two factor 2024-03-25 14:23:32 +01:00
Andras Bacsai
c9160cabc5 Update version numbers and fix layout in subscription views 2024-03-25 14:09:13 +01:00
Andras Bacsai
9e289d5e97 Merge pull request #1866 from coollabsio/next
v4.0.0-beta.242
2024-03-25 13:52:06 +01:00
Andras Bacsai
901a580e11 Update service-templates.json with new compose configuration 2024-03-25 13:48:03 +01:00
Andras Bacsai
d196292551 Merge pull request #1878 from aruator/patch-1
Update pocketbase.yaml
2024-03-25 13:47:41 +01:00
Andras Bacsai
8d856b0ec6 Merge pull request #1883 from procchio6/fix/oauth
Fix azure oauth not recognizing tenant ID
2024-03-25 13:45:45 +01:00
Andras Bacsai
90fad52760 Update Sentry DSN in config file 2024-03-25 13:40:46 +01:00
Andras Bacsai
2817875461 Temporary disable cdn services list 2024-03-25 13:28:39 +01:00
Andras Bacsai
bcbdee1dcc Refactor Docker-related code and add conditional rendering 2024-03-25 13:10:05 +01:00
Andras Bacsai
3de4f2805a Commented out the changeTheme button in the navbar 2024-03-25 12:47:40 +01:00
Andras Bacsai
995197cad9 Update button label and server retrieval logic***
***Refactor email settings form***
***Update project index view layout
2024-03-25 12:47:10 +01:00
Andras Bacsai
89cc4d1df4 Update database URL label to include IP and PORT (public) 2024-03-25 12:36:04 +01:00
Andras Bacsai
61f3b3592f fix: restart service db proxies 2024-03-25 12:35:31 +01:00
Andras Bacsai
8cb6f67a60 fix: make sure service db proxy restarted 2024-03-25 12:17:52 +01:00
Andras Bacsai
7c580f898c fix: public service database 2024-03-25 12:13:43 +01:00
Andras Bacsai
9ad6ce5851 ui ui ui ui 2024-03-25 11:33:38 +01:00
Andras Bacsai
a66090b594 ui + package updates 2024-03-25 10:41:44 +01:00
Andras Bacsai
d992a3f7d7 Update package dependencies 2024-03-25 09:13:38 +01:00
Andras Bacsai
b418a78e2e ui ui ui (pig) 2024-03-24 16:00:25 +01:00
Pat Rocchio
cae9ae51ad Fix azure oauth not recognizing tenant ID
Co-authored-by: Suraj Kumar <srjkmr1024@gmail.com>
Co-authored-by: Michael Castanieto <mcastanieto@gmail.com>
Co-authored-by: Mike Kim <m.kim4247@gmail.com>
2024-03-22 15:22:27 -04:00
Andras Bacsai
04c92ec4bd Update modal input behavior and limit-reached layout 2024-03-22 17:02:09 +01:00
Andras Bacsai
54834891fb Update styles and classes in blade files 2024-03-22 16:52:20 +01:00
Andras Bacsai
c9e2f4244d Fix route names and styling in blade files 2024-03-22 14:47:36 +01:00
Andras Bacsai
503b86ac13 Update navbar styling and fix subtitle padding 2024-03-22 13:47:54 +01:00
Andras Bacsai
ec051eba38 Delete unused notification components and update notification navbar 2024-03-22 13:25:43 +01:00
Andras Bacsai
2f50f64ecf Update input component and toast position 2024-03-22 13:09:02 +01:00
Andras Bacsai
66fe124dd1 Update CSS styles and fix SVG icons 2024-03-22 12:47:19 +01:00
Andras Bacsai
87e56c2f66 Update border color to dark mode 2024-03-22 12:06:16 +01:00
Andras Bacsai
f044b0292c Refactor invitation link handling in invitations.blade.php 2024-03-22 12:03:59 +01:00
Andras Bacsai
ee1d4cd45d Update popup.blade.php styling 2024-03-22 11:46:59 +01:00
Andras Bacsai
b25f83e096 Refactor Docker creation in Destination Show.php 2024-03-22 11:34:33 +01:00
Andras Bacsai
8b7e1e4169 Refactor code and update UI components 2024-03-22 11:34:15 +01:00
Andras Bacsai
ca9a2cb13a fix: compose env has SERVICE, but not defined for Coolify 2024-03-21 15:36:37 +01:00
Andras Bacsai
93af92743c Refactor modal confirmation layout 2024-03-21 15:00:14 +01:00
Andras Bacsai
0ebef3792d Refactor resource table layout in change.blade.php 2024-03-21 14:49:13 +01:00
Andras Bacsai
7a3bb14653 Refactor password validation and update button label 2024-03-21 14:46:34 +01:00
Andras Bacsai
fbc0a39a1c Refactor navbar links to use "Sponsor us" instead of "Help us" 2024-03-21 14:32:09 +01:00
Andras Bacsai
473bad24b7 ux ux ux ux ui ui ui ui 2024-03-21 14:30:35 +01:00
aruator
313d968985 Update pocketbase.yaml
add a volume mount to persist pocketbase hooks
2024-03-21 12:59:12 +01:00
Andras Bacsai
b5775ff9d2 ui ui ui ui ui ui ux ux ux ux ux ux 2024-03-21 12:44:32 +01:00
Andras Bacsai
8f66a41c09 Remove unnecessary button code in upgrade.blade.php 2024-03-21 08:41:23 +01:00
Andras Bacsai
6721471c63 Update PHP image versions in Dockerfiles 2024-03-20 20:30:46 +01:00
Andras Bacsai
638421de40 Update release version to 4.0.0-beta.242 2024-03-20 16:43:19 +01:00
Andras Bacsai
7bc5338cb3 Merge branch 'main' into next 2024-03-20 16:42:55 +01:00
Andras Bacsai
07c9db9b54 fix: shared env variable parsing 2024-03-20 16:41:10 +01:00
Andras Bacsai
fafc4fb71e Update UI elements and text content 2024-03-20 15:46:59 +01:00
Andras Bacsai
6b49d32102 Merge pull request #1833 from procchio6/feature/oauth
Add oauth support
2024-03-20 14:00:06 +01:00
Andras Bacsai
3a391aa3cb Merge branch 'next' into feature/oauth 2024-03-20 13:59:56 +01:00
Andras Bacsai
42019321e3 Merge branch 'feature' into feature/oauth 2024-03-20 13:58:31 +01:00
Andras Bacsai
b61860b3ab ui: redesign 2024-03-20 12:54:06 +01:00
Andras Bacsai
91950e1891 ui: redesign 2024-03-19 15:37:16 +01:00
Andras Bacsai
42715bba50 fix: volumes for prs 2024-03-19 11:48:25 +01:00
Andras Bacsai
0aacad655d Refactor previews.blade.php to improve code readability 2024-03-19 11:05:02 +01:00
Andras Bacsai
289480c954 feat: able to run scheduler/horizon programatically 2024-03-19 10:24:23 +01:00
Andras Bacsai
3da7746629 update packages in helper container 2024-03-19 10:24:09 +01:00
Andras Bacsai
8d48051a8d Update Node.js version in Dockerfile 2024-03-19 10:22:35 +01:00
Andras Bacsai
ec16c0f0f4 update cloudflared version 2024-03-19 10:21:40 +01:00
Andras Bacsai
19d19112d9 Add healthcheck for Umami service and update service-templates.json 2024-03-18 15:40:25 +01:00
Andras Bacsai
d9f1c2a406 Merge pull request #1861 from coollabsio/next
v4.0.0-beta.240
2024-03-18 15:21:41 +01:00
Andras Bacsai
ef2be40478 Refactor dashboard view to remove unnecessary code and improve readability 2024-03-18 14:35:46 +01:00
Andras Bacsai
8e2ee5e5e4 fix: dashboard view if no project found 2024-03-18 14:32:49 +01:00
Andras Bacsai
c3da2bfade Update Supabase port to 8000 2024-03-18 14:00:26 +01:00
Andras Bacsai
bbd6780971 Add port configuration for Supabase service 2024-03-18 13:03:56 +01:00
Andras Bacsai
0a6dab1f24 fix: raw compose deployment 2024-03-18 12:40:58 +01:00
Andras Bacsai
faa9a982a9 fix: multiline input 2024-03-18 12:28:53 +01:00
Andras Bacsai
de8bb8a951 fix: custom ip address should turn off rolling update 2024-03-18 12:18:11 +01:00
Andras Bacsai
b766eef5ef fix: consistent container name 2024-03-18 12:13:08 +01:00
Andras Bacsai
a185787044 fix: 0 in env value 2024-03-18 11:49:26 +01:00
Andras Bacsai
76f7cd08ee fix: only escape envs after v239+ 2024-03-18 11:36:36 +01:00
Andras Bacsai
460451bcce Update function to handle edge cases 2024-03-18 11:09:16 +01:00
Andras Bacsai
71edb68995 Update GOTRUE_SITE_URL environment variable 2024-03-18 11:09:00 +01:00
Andras Bacsai
e188482247 Merge pull request #1842 from estubmo/supabase
Supabase Fixes - Edge Functions and Email Redirect
2024-03-18 11:08:37 +01:00
Andras Bacsai
047d320665 Merge branch 'next' into supabase 2024-03-18 11:08:28 +01:00
Andras Bacsai
5c4d9a85be Merge pull request #1845 from hamanuha/patch-1
Supabase: Fixes for Realtime
2024-03-18 11:06:43 +01:00
Andras Bacsai
56c08056d2 Merge pull request #1858 from Omikorin/patch-1
fix: empty get logs number of lines
2024-03-18 11:05:20 +01:00
Andras Bacsai
b39ac73cd8 fix: server stopped, service page not reachable 2024-03-18 08:53:44 +01:00
Michał Korczak
c9054e7d8c fix: empty get logs number of lines 2024-03-17 18:27:01 +01:00
Andras Bacsai
adf5c9bd46 Refactor environment variable handling in ApplicationDeploymentJob.php 2024-03-15 22:16:22 +01:00
Andras Bacsai
3ea3674407 fix: multiline env variables 2024-03-15 22:02:37 +01:00
Andras Bacsai
657c7d8cff Refactor form components
Update Input and Textarea components to use nullable type declarations and remove unused imports.
2024-03-15 20:40:50 +01:00
Manuel
350e32326f Fix app name of realtime for logging 2024-03-15 13:25:49 +01:00
Manuel
1894573c2f Change service name directly 2024-03-15 09:19:30 +01:00
Manuel
73f889ac9f Fix realtime container name and env var 2024-03-15 07:11:05 +01:00
Andras Bacsai
a336dae84c fix: $ in env variable
feat: multiline envs
2024-03-14 23:00:06 +01:00
Andras Bacsai
467c826c04 Merge pull request #1843 from coollabsio/next
v4.0.0-beta.239
2024-03-14 21:23:36 +01:00
Andras Bacsai
a4c164a57e fix: duplicate dockerfile 2024-03-14 21:19:37 +01:00
Eirik Mo
8c0c22a925 Merge branch 'main' into supabase 2024-03-14 19:21:02 +01:00
Eirik Mo
c8d528ffc4 fix: supabase edge functions
fix: GOTRUE_SITE_URL should be SERVICE_SITE_URL for correct redirects
fix: DB_AFTER_CONNECT_QUERY value will be incorrecly resolved if it's inside quotes
chore: update image versions
2024-03-14 19:18:07 +01:00
Andras Bacsai
bb7b1f9e0c Merge pull request #1841 from coollabsio/next
v4.0.0-beta.238
2024-03-14 19:16:35 +01:00
Andras Bacsai
f994f83ce1 revert validateCompose until further investigation 2024-03-14 19:15:30 +01:00
Andras Bacsai
2af083b2e5 Merge pull request #1816 from coollabsio/next
4.0.0-beta.237
2024-03-14 10:34:34 +01:00
Andras Bacsai
de4d0961da Add Sleep class and refactor container name in generate_compose_file() 2024-03-14 10:10:03 +01:00
Andras Bacsai
c8d48ccbff fix: docker compose validation 2024-03-14 09:21:48 +01:00
Andras Bacsai
695d3b82b5 Add toast timeout functionality 2024-03-14 09:10:32 +01:00
Andras Bacsai
965625ad01 fix: create initial files async 2024-03-13 18:26:30 +01:00
Andras Bacsai
379733938c Update environment variable description 2024-03-13 15:07:43 +01:00
Andras Bacsai
3198999746 Fix null check for memory swappiness and CPU shares 2024-03-13 15:00:29 +01:00
Andras Bacsai
1f03499fc5 feat: show resources on source page 2024-03-13 14:55:44 +01:00
Andras Bacsai
a53d888747 Add pre-deployment and post-deployment commands 2024-03-13 14:41:31 +01:00
Andras Bacsai
62905f084f Merge pull request #1723 from stooit/feat/post-deploy-command
Adds basic support for pre/post-deployment commands.
2024-03-13 14:34:44 +01:00
Andras Bacsai
ba7ee4fba7 Remove Penpot environment file and update preselect branch URL 2024-03-13 14:34:19 +01:00
Andras Bacsai
6cb3df9350 rename boarding to onboarding 2024-03-13 12:11:37 +01:00
Andras Bacsai
5c1c71c625 Merge pull request #1825 from hades200082/main
Added Penpot service
2024-03-13 12:11:13 +01:00
Andras Bacsai
7fd0cfc85f Merge pull request #1831 from mmbytesolutions/next
ui: visual feedback when container is unhealthy
2024-03-13 12:04:59 +01:00
Andras Bacsai
efad3b1284 Merge pull request #1823 from fabiangigler/patch-1
improve onboarding messages
2024-03-13 12:04:15 +01:00
Andras Bacsai
a06de9682c Add default 404 redirect for Caddy proxy 2024-03-13 11:25:34 +01:00
Andras Bacsai
0d4ad05c1c Add custom_healthcheck_found flag to ApplicationDeploymentJob 2024-03-13 10:50:05 +01:00
Andras Bacsai
aef088a9d2 fix: consider custom healthchecks in dockerfile 2024-03-13 10:44:15 +01:00
Andras Bacsai
e8f3aa681e fix: failed deployments should send failed email/notification 2024-03-13 10:24:45 +01:00
Andras Bacsai
42293fb11a feat: reset password 2024-03-13 10:10:53 +01:00
Andras Bacsai
65e0eb5205 Refactor dynamic configurations and update textarea rows 2024-03-13 09:32:42 +01:00
Andras Bacsai
2f1a7f8f40 Update service environment variables 2024-03-13 09:27:42 +01:00
Andras Bacsai
73e9410264 Add port configuration for services 2024-03-12 20:03:11 +01:00
Andras Bacsai
c835c02bf2 Update port numbers for services 2024-03-12 19:09:08 +01:00
Andras Bacsai
336d44a5cc Refactor fqdnLabelsForCaddy function to handle serviceLabels parameter 2024-03-12 19:08:52 +01:00
Andras Bacsai
7027931095 Add port configuration for services 2024-03-12 17:47:32 +01:00
Andras Bacsai
87b56d538d Add Grafana service configuration and update service templates 2024-03-12 17:43:00 +01:00
Andras Bacsai
0519ce2001 Fix reloadCaddy() method call in addDynamicConfiguration() 2024-03-12 17:36:32 +01:00
Andras Bacsai
d4d0330f70 Fix reloadCaddy() method call in delete() function 2024-03-12 17:35:53 +01:00
Andras Bacsai
25ae54cab7 fix: service ports for services + caddy 2024-03-12 15:09:24 +01:00
Andras Bacsai
a67576b447 Update reverse proxy configuration in docker.php 2024-03-12 13:01:15 +01:00
Andras Bacsai
4d181eef8e Refactor proxy type retrieval in Server and Proxy classes 2024-03-12 12:45:55 +01:00
Andras Bacsai
1835a91467 fix: proxy switch 2024-03-12 12:30:40 +01:00
Andras Bacsai
bcc61b0d8b Add reverse proxy configuration for coolify-realtime 2024-03-12 11:34:57 +01:00
Andras Bacsai
f8055e7976 fix: /realtime endpoint 2024-03-12 11:32:40 +01:00
Andras Bacsai
2509406d1c fix: startproxy event
fix: add data to async remove processes
2024-03-12 11:22:02 +01:00
Andras Bacsai
b3d15f91e4 Remove skip(10) from activity_log and webhooks cleanup 2024-03-12 10:58:31 +01:00
Andras Bacsai
85c36df2a3 Refactor database cleanup queries to keep the last 10 entries 2024-03-12 10:58:28 +01:00
Andras Bacsai
6ef79f5213 Refactor database cleanup command to include dry-run mode 2024-03-12 10:57:07 +01:00
Andras Bacsai
b576014d07 fix: reload caddy issue 2024-03-12 10:42:56 +01:00
Pat Rocchio
1f37318f79 Add oauth support
- Support azure, bitbucket, github, gitlab, google providers
- Add authentication page to settings

Co-authored-by: Suraj Kumar <srjkmr1024@gmail.com>
Co-authored-by: Michael Castanieto <mcastanieto@gmail.com>
Co-authored-by: Mike Kim <m.kim4247@gmail.com>
2024-03-11 17:29:57 -04:00
Andras Bacsai
6950966b06 Commented out reloadCaddy() calls in DynamicConfigurationNavbar.php, NewDynamicConfiguration.php, and Server.php 2024-03-11 20:39:41 +01:00
Andras Bacsai
8eacf67725 Remove unnecessary code in StartProxy.php 2024-03-11 20:25:35 +01:00
Andras Bacsai
52120e7a38 Add dynamic proxy configuration setup in StartProxy.php and update proxyPath() in Server.php 2024-03-11 20:17:37 +01:00
Andras Bacsai
1490828069 feat: dynamic configuration for caddy 2024-03-11 17:31:28 +01:00
Andras Bacsai
9bdad6bb67 feat caddy dynamic configurations 2024-03-11 17:17:34 +01:00
Andras Bacsai
f24063cfea Add CADDY_DOCKER_POLLING_INTERVAL environment variable 2024-03-11 15:36:45 +01:00
Andras Bacsai
1defed27a0 Refactor compose file generation and add link to documentation 2024-03-11 15:19:04 +01:00
Andras Bacsai
34d6a12d95 feat: experimental caddy support 2024-03-11 15:08:05 +01:00
Andras Bacsai
5d3de967f0 fix: fqdn null in case docker compose bp 2024-03-11 09:42:16 +01:00
Andras Bacsai
8b73f9da17 fix: deploy api messages 2024-03-11 09:42:02 +01:00
Mr. Mendez
820099622e visual feedback when container is unhealthy 2024-03-10 18:14:53 +00:00
Lee Conlin
366d39a7a3 Added some default env values for penpot 2024-03-09 00:21:47 +00:00
Lee Conlin
853a14c6b8 Added PenPot service template 2024-03-09 00:21:35 +00:00
Fabian Gigler
15ca68f7e1 improve onboarding messages 2024-03-08 18:44:42 +01:00
Andras Bacsai
8b9548a463 Refactor resource mapping in resources() method 2024-03-08 15:16:58 +01:00
Andras Bacsai
6688120aee Update team-by-id-members API documentation link 2024-03-07 15:46:27 +01:00
Andras Bacsai
93e4e723fa Add documentation links to error responses in Team controller 2024-03-07 15:45:49 +01:00
Andras Bacsai
8b74e50c50 Add two-factor authentication fields to hidden array in User model 2024-03-07 13:05:04 +01:00
Andras Bacsai
129a644781 ui: make notifications separate view
fix: popup if no notifications are set
2024-03-07 12:58:04 +01:00
Andras Bacsai
bbfbd4a105 Refactor domain handling in API controller 2024-03-07 12:36:49 +01:00
Andras Bacsai
9d31d990fc Update error message for missing resources 2024-03-07 12:35:38 +01:00
Andras Bacsai
c7f15c42fa feat: add deployments api 2024-03-07 12:27:23 +01:00
Andras Bacsai
515d401746 feat: add deployment details to deploy endpoint 2024-03-07 12:22:18 +01:00
Andras Bacsai
2a03b452d3 feat: team api endpoint 2024-03-07 12:01:21 +01:00
Andras Bacsai
7aa8c765f6 Refactor domain IP handling in Domains controller 2024-03-07 11:49:15 +01:00
Andras Bacsai
038f65aae6 Add InstanceSettings model and update IP handling in domains controller 2024-03-07 11:42:16 +01:00
Andras Bacsai
db24828a5a Refactor resource retrieval in API controller 2024-03-07 11:37:56 +01:00
Andras Bacsai
c7693d0ec3 feat: resources api endpoint 2024-03-07 11:35:00 +01:00
Andras Bacsai
5e2afd4b4d Refactor domain grouping in domains API controller 2024-03-07 11:25:15 +01:00
Andras Bacsai
7a21312daf feat: domains api endpoint 2024-03-07 11:14:03 +01:00
Andras Bacsai
051a1405e7 Refactor backup execution and cleanup functionality 2024-03-07 10:27:21 +01:00
Andras Bacsai
a6669ed876 Update version and add check for Docker installed via snap 2024-03-07 09:59:19 +01:00
Andras Bacsai
f1b00436aa Update link target in stack-form.blade.php 2024-03-07 09:56:09 +01:00
Andras Bacsai
e699103d3e Update application names and base directories 2024-03-06 15:34:21 +01:00
Andras Bacsai
ba9cb88ca3 Update slogans and healthcheck command in next-image-transformation.yaml and service-templates.json 2024-03-06 10:58:08 +01:00
Andras Bacsai
365850d922 Update version numbers + add next-image-transformation service 2024-03-06 10:57:39 +01:00
Andras Bacsai
46ed17c99e Merge pull request #1812 from coollabsio/next
v4.0.0-beta.236
2024-03-05 16:25:08 +01:00
Andras Bacsai
fadff798a7 Update server installation method 2024-03-05 16:23:46 +01:00
Andras Bacsai
81512bb3b7 Update server and version configurations 2024-03-05 15:47:43 +01:00
Andras Bacsai
3dd00dd91a Merge pull request #1811 from coollabsio/next
v4.0.0-beta.235
2024-03-05 10:29:41 +01:00
Andras Bacsai
fd97c5085b Update devDependencies in package.json 2024-03-05 10:26:32 +01:00
Andras Bacsai
fa6a249fb4 Update Docker installation for AlmaLinux 2024-03-05 10:24:02 +01:00
Andras Bacsai
0aa9b1735b Update currentState in selectExistingServer method 2024-03-05 10:07:28 +01:00
Andras Bacsai
6027bee3b8 Refactor repository selection logic in GithubPrivateRepository.php 2024-03-05 09:58:02 +01:00
Andras Bacsai
4d72787c83 fix: sort repositories by name 2024-03-05 09:40:38 +01:00
Andras Bacsai
c6740cfea0 fix: make sure to show some buttons 2024-03-05 09:37:35 +01:00
Andras Bacsai
9c1d585c43 Fix condition to return current team if user has teams 2024-03-05 09:22:38 +01:00
Andras Bacsai
863acf988e Fix selected repository ID assignment in loadRepositories method 2024-03-05 09:21:12 +01:00
Andras Bacsai
a6b3beafbb Fix issue with loading repositories in GithubPrivateRepository.php 2024-03-05 09:20:50 +01:00
Andras Bacsai
2ffc3f497b fix: should note delete personal teams 2024-03-05 09:19:15 +01:00
Andras Bacsai
f3a279be26 revert delayed jobs 2024-03-05 08:49:11 +01:00
Andras Bacsai
9ad6631747 Update version numbers 2024-03-04 14:32:17 +01:00
Andras Bacsai
0131f5e341 Merge pull request #1807 from coollabsio/next
v4.0.0-beta.234
2024-03-04 13:40:42 +01:00
Andras Bacsai
b5ab9a8da6 Add custom docker run options for application 2024-03-04 13:39:34 +01:00
Andras Bacsai
57fa2709da Add font preloading and DNS prefetching 2024-03-04 13:34:20 +01:00
Andras Bacsai
96c6a198d7 Fix base64 encoding for TOTP_VAULT_KEY 2024-03-04 12:50:56 +01:00
Andras Bacsai
d106d4bd4e Refactor generateEnvValue function to use base64 encoding for certain cases 2024-03-04 12:46:37 +01:00
Andras Bacsai
53cd3091f7 Add Directus service fields to extraFields method 2024-03-04 12:46:33 +01:00
Andras Bacsai
f1e7b870aa Add TOTP_VAULT_KEY environment variable to Plausible service 2024-03-04 12:30:32 +01:00
Andras Bacsai
99fe076b5a Add scheduled task for database cleanup if not in cloud environment 2024-03-04 12:17:33 +01:00
Andras Bacsai
65fcaa17d9 Update exception in PreventRequestsDuringMaintenance middleware and version numbers 2024-03-04 11:41:02 +01:00
Andras Bacsai
89c6563e00 Merge pull request #1806 from coollabsio/next
v4.0.0-beta.233
2024-03-04 11:18:56 +01:00
Andras Bacsai
76b0bef32e Update slogan and logo for changedetection service 2024-03-04 11:17:05 +01:00
Andras Bacsai
c20aa0b256 Refactor method names to use camel case 2024-03-04 11:01:14 +01:00
Andras Bacsai
b4908cfcb4 Merge pull request #1804 from RayBB/change-detection
add changedetection.io template
2024-03-04 10:47:54 +01:00
Andras Bacsai
4fb5b04d27 Update proxy configuration layout 2024-03-04 10:46:53 +01:00
Andras Bacsai
8385bbb0a0 feat: gzip enabled & stipprefix setting
refactor: code
2024-03-04 10:46:13 +01:00
Andras Bacsai
cee6b54033 Add proxy start functionality when selecting a proxy type 2024-03-04 10:42:54 +01:00
Andras Bacsai
0dd591a5ff fix: raw compose make dirs
fix: raw compose add coolify labels
2024-03-04 10:13:40 +01:00
Andras Bacsai
62278126e4 fixes 2024-03-04 09:12:23 +01:00
Andras Bacsai
0aa85a3701 fix: service status updated 2024-03-04 08:57:18 +01:00
Andras Bacsai
0e1ba64836 fix: sentry error 2024-03-04 08:51:24 +01:00
Andras Bacsai
f7e1ce8656 fix: env value generation 2024-03-04 08:49:53 +01:00
RayBB
5030c14dc2 add changedetection.io template 2024-03-03 23:42:33 +01:00
Andras Bacsai
1333cd1d84 Merge pull request #1799 from coollabsio/next
v4.0.0-beta.232
2024-03-02 16:04:06 +01:00
Andras Bacsai
112c259d27 Refactor destinations method in Server model 2024-03-02 15:58:02 +01:00
Andras Bacsai
130d1e1756 Update DockerCleanupJob and version numbers 2024-03-02 15:18:49 +01:00
Andras Bacsai
a7df9fa625 Merge pull request #1798 from coollabsio/next
v4.0.0-beta.231
2024-03-02 15:04:48 +01:00
Andras Bacsai
9064aedc89 Fix server reference in ExecuteContainerCommand.php 2024-03-02 15:02:55 +01:00
Andras Bacsai
fda5d23d32 feat: logs and execute commands with several servers 2024-03-02 14:55:39 +01:00
Andras Bacsai
60be51dbe0 Update pull_request_id comparison in ApplicationDeploymentJob.php and update version numbers 2024-03-02 13:22:05 +01:00
Andras Bacsai
e9f451339f Merge pull request #1796 from coollabsio/next
fix: unmanaged containers method
2024-03-01 19:14:18 +01:00
Andras Bacsai
4d8ffd05a9 Refactor unmanagedContainers property in Resources.php and add conditional return in loadUnmanagedContainers() method 2024-03-01 19:13:22 +01:00
Andras Bacsai
b630105572 Merge pull request #1795 from coollabsio/next
v4.0.0-beta.230
2024-03-01 19:09:09 +01:00
Andras Bacsai
9fa71f847f Refactor notification channels based on cloud environment 2024-03-01 19:08:00 +01:00
Andras Bacsai
f70a9c6974 Fix notification channels in ApplicationDeploymentJob and DeploymentSuccess 2024-03-01 19:07:21 +01:00
Andras Bacsai
a4d173c733 Fix unmanagedContainers type declaration 2024-03-01 19:00:45 +01:00
Andras Bacsai
2eb7712e09 fix: remove success application deployment job
wip: daily backup status
2024-03-01 18:24:14 +01:00
Andras Bacsai
54923b7640 feat: collect webhooks during maintenance 2024-03-01 14:04:29 +01:00
Andras Bacsai
bb927505fe Merge pull request #1793 from coollabsio/next
v4.0.0-beta.229
2024-03-01 11:47:14 +01:00
Andras Bacsai
5e66e314d2 Update version numbers to 4.0.0-beta.229 2024-03-01 11:44:01 +01:00
Andras Bacsai
6fe791c1f1 fix: pull request deployments + build servers 2024-03-01 11:43:42 +01:00
Andras Bacsai
860c537f81 Add server limit override for development environment 2024-03-01 11:41:28 +01:00
Andras Bacsai
a352e4cbf7 fix: public prs should not be commented 2024-03-01 11:41:22 +01:00
Andras Bacsai
5322d446bd fix: service container status updates 2024-03-01 10:36:32 +01:00
Andras Bacsai
604ab0afd8 Add autofocus to search input field 2024-03-01 10:06:59 +01:00
Andras Bacsai
3d87a88d3d Merge pull request #1790 from coollabsio/next
v4.0.0-beta.228
2024-03-01 09:32:19 +01:00
Andras Bacsai
10f9e22a8e fix: do not show n/a networsk 2024-03-01 09:28:14 +01:00
Andras Bacsai
8edda0cdda fix: load unmanaged async 2024-03-01 09:25:27 +01:00
Andras Bacsai
21047afc02 Add new sponsor image 2024-03-01 09:19:23 +01:00
Andras Bacsai
2e9793ffb2 Refactor code for improved performance and readability 2024-02-29 09:21:02 +01:00
Andras Bacsai
fcd100df39 Fix typos and grammatical errors in email templates and form view 2024-02-29 09:16:02 +01:00
Andras Bacsai
dfd564a3a4 Add Supabase logo and update environment variable in compose file 2024-02-29 09:15:06 +01:00
Andras Bacsai
a43c916009 Refactor code and add new fields for Kong service 2024-02-28 13:48:39 +01:00
Andras Bacsai
c8332ca9bf fix: resource tab not loading if server is not reachable 2024-02-28 09:51:45 +01:00
Andras Bacsai
e98170f921 Update Github Sponsors to $40+ 2024-02-28 09:38:59 +01:00
Andras Bacsai
b8f25406cd Refactor code to improve performance and readability 2024-02-27 15:44:19 +01:00
Andras Bacsai
76dcc12b13 Update version numbers to 4.0.0-beta.228 2024-02-27 15:13:30 +01:00
Andras Bacsai
baa2228c9b Merge pull request #1786 from coollabsio/next
v4.0.0-beta.226
2024-02-27 09:10:31 +01:00
Andras Bacsai
5275ae8e9c Refactor getLogs method and update view template 2024-02-27 09:08:15 +01:00
Andras Bacsai
c71e1e107e Refactor getLogs method and update get-logs.blade.php view 2024-02-27 09:05:28 +01:00
Andras Bacsai
8ab72c7e10 feat: preview deployment logs 2024-02-27 09:01:19 +01:00
Andras Bacsai
a8970df91b Update class names in controllers 2024-02-27 08:03:42 +01:00
Andras Bacsai
2468251f56 Merge pull request #1783 from coollabsio/next
v4.0.0-beta.226
2024-02-26 14:31:16 +01:00
Andras Bacsai
6e74f3e40e Merge pull request #1779 from Rei-x/next
Fix import to mysql and mariadb
2024-02-26 14:30:35 +01:00
Andras Bacsai
407f84a4bb Refactor Dockerfile location handling in ApplicationDeploymentJob.php 2024-02-26 14:28:02 +01:00
Andras Bacsai
91632f0adb fix: custom dockerfile location always checked 2024-02-26 14:26:19 +01:00
Andras Bacsai
af3c575d84 fix: server disabled 2024-02-26 14:22:24 +01:00
Andras Bacsai
bf1475441d Update service stop message and fix sidebar alignment 2024-02-26 12:38:15 +01:00
Andras Bacsai
9268f9db1d Refactor user switching logic and update UI 2024-02-26 11:48:35 +01:00
Andras Bacsai
600c43827a Update server check and version numbers 2024-02-26 11:25:38 +01:00
Andras Bacsai
74092ea95b Merge pull request #1776 from coollabsio/next
4.0.0-beta.225
2024-02-26 11:08:53 +01:00
Andras Bacsai
b67abe58e8 Remove commented out code in ServerStatusJob.php 2024-02-26 10:34:44 +01:00
Andras Bacsai
678647f39a fix: force enable/disable server in case ultimate package quantity decreases 2024-02-26 10:25:21 +01:00
Andras Bacsai
453956172b Refactor show.blade.php to improve code readability 2024-02-26 09:32:28 +01:00
Andras Bacsai
b550c32f9b Add whitespace-pre-line class to font-mono in deployment show blade file 2024-02-26 09:09:01 +01:00
Andras Bacsai
f6b886adbc revert delayed jobs for now 2024-02-26 08:52:46 +01:00
Andras Bacsai
9642453052 fix: firefly service 2024-02-26 08:52:17 +01:00
Andras Bacsai
64fca99c26 feat: server disabled by overflow 2024-02-25 23:34:01 +01:00
Andras Bacsai
c7da43f50d feat: add static ipv4 ipv6 support 2024-02-25 23:13:27 +01:00
Rei
6efa2dd9ba fix: import to mysql and mariadb 2024-02-25 22:15:48 +01:00
Andras Bacsai
5e980c5fe0 Update pricing plans layout and text 2024-02-25 22:14:20 +01:00
Andras Bacsai
c8c7a415ea Add new Livewire component and update subscription actions 2024-02-25 22:08:44 +01:00
Andras Bacsai
c3cfb8d23b Refactor getRecepients method and fix serverLimitReached method in Team model 2024-02-25 18:22:24 +01:00
Andras Bacsai
1b055f0316 Refactor subscription pricing and update server limit 2024-02-25 14:00:35 +01:00
Andras Bacsai
1fcbf0b363 Update pricing plans display and button text 2024-02-23 22:14:24 +01:00
Andras Bacsai
61dbc81765 feat: delay container/server jobs 2024-02-23 21:51:43 +01:00
Andras Bacsai
b8b76dfa40 Refactor CleanupQueue to CleanupDatabase 2024-02-23 21:05:48 +01:00
Andras Bacsai
297b314904 feat: custom server limit 2024-02-23 15:45:53 +01:00
Andras Bacsai
55dd1ab0a1 Update cleanup script and version numbers 2024-02-23 14:39:52 +01:00
Andras Bacsai
8c803f1c4b Merge pull request #1775 from coollabsio/next
v4.0.0-beta.224
2024-02-23 13:57:11 +01:00
Andras Bacsai
3b942049a2 Refactor subscription handling logic in middleware and model 2024-02-23 13:50:48 +01:00
Andras Bacsai
f78fd212bb fix: subscription / plan switch, etc 2024-02-23 12:59:14 +01:00
Andras Bacsai
f931ebece8 feat: make user owner
fix: ownership check
2024-02-23 12:34:36 +01:00
Andras Bacsai
ea0a9763bf Update navbar icons 2024-02-23 11:23:14 +01:00
Andras Bacsai
b59e47dcf9 fix: stripe invoice paid webhook
fix: prepare customer initiated tier change
fix: separate view for subscriptions
2024-02-23 11:21:14 +01:00
Andras Bacsai
ce09ef8848 Merge pull request #1774 from steveworley/fix/ux-hamburger-extra-menu-options
Fix: Change + icon to hamburger.
2024-02-23 10:18:38 +01:00
Andras Bacsai
188727daba Update version numbers 2024-02-23 10:14:32 +01:00
Andras Bacsai
1150633fef fix: unknown image of service until it is uploaded 2024-02-23 10:14:13 +01:00
Andras Bacsai
62ae845f4b fix: complex service status
service: firefly III
2024-02-23 10:09:42 +01:00
Steve Worley
0757fd741e Fix: Change + icon to hamburger. 2024-02-23 09:21:11 +10:00
Andras Bacsai
a07fa8ccd2 Merge pull request #1773 from coollabsio/next
v4.0.0-beta.223
2024-02-22 15:23:27 +01:00
Andras Bacsai
c77f32e696 fix: statuses 2024-02-22 15:15:16 +01:00
Andras Bacsai
4391771416 Merge pull request #1768 from coollabsio/next
v4.0.0-beta.222
2024-02-22 14:56:55 +01:00
Andras Bacsai
01ab820459 Fix error message formatting in handleError function 2024-02-22 14:56:41 +01:00
Andras Bacsai
c7218f2856 Update success messages 2024-02-22 14:53:42 +01:00
Andras Bacsai
592221b4bf fix: server validation 2024-02-22 14:46:11 +01:00
Andras Bacsai
836458ad85 fix: no coolify.yaml found 2024-02-22 14:45:56 +01:00
Andras Bacsai
7233c86f3d fix: use latest image if nothing is specified 2024-02-22 14:45:41 +01:00
Andras Bacsai
154b1b05e4 feat: able to add dynamic configurations from proxy dashboard 2024-02-22 13:29:28 +01:00
Andras Bacsai
4d88638d4d Update proxy configuration in bootstrap/helpers/proxy.php 2024-02-22 12:00:16 +01:00
Andras Bacsai
9403986643 Merge pull request #1767 from iamEvanYT/fix-stuck-connections
fix: connections being stuck and not processed until proxy restarts
2024-02-22 11:59:52 +01:00
Andras Bacsai
ad48610b2f Merge pull request #1770 from piscis/patch-2
fix: Avoid breaking the tile layout with long fqdn output
2024-02-22 11:54:25 +01:00
Andras Bacsai
63487cf3ec feat: minversion for services 2024-02-22 11:53:25 +01:00
Andras Bacsai
4ae2087c2e fix: server validation 2024-02-22 11:28:45 +01:00
Andras Bacsai
5179129a6b fix: complex container status
feat: able to change primary server
feat: links inside the logs
2024-02-22 10:57:05 +01:00
Andras Bacsai
6a00d8c88c Refactor loadData method in Destination.php 2024-02-22 09:38:09 +01:00
Andras Bacsai
50f43f9396 Update resource view to set type as 'public' 2024-02-22 09:38:03 +01:00
Alex
6f205f8931 Force browser to break all words on line end for fqdn output
Force the browser to break long lines for the fqdn output instead of overflowing the tile
2024-02-21 19:16:09 +01:00
Andras Bacsai
3776ffa49b disable administration gh permission for now 2024-02-21 14:42:38 +01:00
Andras Bacsai
318b5beac1 Update select.blade.php with responsive styling 2024-02-21 14:40:48 +01:00
Andras Bacsai
344c5d6d12 Update service configurations 2024-02-21 14:30:32 +01:00
Andras Bacsai
4d319a8caa Update service and shared helper files 2024-02-21 12:22:32 +01:00
Andras Bacsai
74b24a0690 Add file permission change for LocalFileVolume.php and add service_name parameter to fqdnLabelsForTraefik() function 2024-02-21 11:21:11 +01:00
Andras Bacsai
1ca0464957 fix: permission change updates from webhook 2024-02-20 20:17:04 +01:00
Andras Bacsai
f7ebc8a88c feat: save github app permission locally 2024-02-20 18:14:47 +01:00
Andras Bacsai
a102099ac1 icons 2024-02-20 17:08:16 +01:00
Andras Bacsai
59ac22aa05 Update redis.svg icon 2024-02-20 15:45:30 +01:00
Andras Bacsai
cd7244b3d7 Add logos for various services 2024-02-20 15:42:30 +01:00
Andras Bacsai
f81b676abe ui: updates 2024-02-20 15:07:12 +01:00
iamEvan
234b154053 fix: connections being stuck and not processed until proxy restarts 2024-02-20 17:16:43 +08:00
Andras Bacsai
a1d09ad574 Update version numbers to 4.0.0-beta.222 2024-02-19 13:39:05 +01:00
Andras Bacsai
b983b23e7e Merge pull request #1766 from coollabsio/next
v4.0.0-beta.221
2024-02-19 13:29:58 +01:00
Andras Bacsai
0b81e77a94 fix: database status 2024-02-19 13:28:14 +01:00
Andras Bacsai
b8cf314bfe fix: submodule cloning 2024-02-19 13:22:09 +01:00
Andras Bacsai
4a3338e59c Update version numbers 2024-02-19 10:44:52 +01:00
Andras Bacsai
5b8538c0f4 Merge pull request #1763 from coollabsio/next
v4.0.0-beta.220
2024-02-19 09:53:54 +01:00
Andras Bacsai
88d6320d08 Re-enable docker container removal in ApplicationDeploymentJob 2024-02-19 09:51:39 +01:00
Andras Bacsai
651c9c2c9b Merge pull request #1756 from victor-teles/fix/log-drain-parsers
fix: fluent bit parsers.conf indentation level
2024-02-19 09:22:36 +01:00
Andras Bacsai
8dd45cd388 Merge pull request #1757 from victor-teles/fix/revalidate-server-button
fix(server): revalidate server button not showing in server's page
2024-02-19 09:21:50 +01:00
Andras Bacsai
126ac354d5 fix: empty build variables 2024-02-19 09:19:50 +01:00
Victor
024769c402 fix(server): revalidate server button not showing in server's page 2024-02-17 12:43:49 -03:00
Victor
5acf141669 fix: fluent bit ident level 2024-02-17 12:25:48 -03:00
Andras Bacsai
92e3e8ab7b Merge branch 'main' into next 2024-02-17 16:17:01 +01:00
Andras Bacsai
187a29c666 Update README.md 2024-02-17 16:16:42 +01:00
Andras Bacsai
4c24631795 Add coolify.managed flag to proxy configuration 2024-02-16 23:17:29 +01:00
Andras Bacsai
7d6bd10cca Add Docker container management methods and update Livewire component 2024-02-16 23:09:35 +01:00
Andras Bacsai
f8c86769a7 fix: resources 2024-02-16 22:15:18 +01:00
Andras Bacsai
e0b0dda382 Remove unused code for displaying server resources 2024-02-16 22:04:26 +01:00
Andras Bacsai
b8708f086e feat: initial api endpoints
feat: server resources are now looks better
2024-02-16 21:56:38 +01:00
Andras Bacsai
3539e4dce9 Update Docker installation error messages 2024-02-16 09:06:28 +01:00
Andras Bacsai
5fdadcf557 fix: add openbsd ssh server check 2024-02-16 09:04:32 +01:00
Andras Bacsai
acb3f01f79 Merge pull request #1752 from ahmedrowaihi/main
👌 IMPORVE(SCRIPT/INSTALL): Support Archlinux
2024-02-16 09:03:51 +01:00
Andras Bacsai
83becdb19d fix: only show redeployment required if status is not exited 2024-02-16 08:34:30 +01:00
=
e3e8fe7895 👌 IMPORVE(SCRIPT/INSTALL): Support Archlinux 2024-02-16 01:56:16 +03:00
Andras Bacsai
6ddff8fae1 Merge pull request #1748 from coollabsio/next
v4.0.0-beta.219
2024-02-15 21:33:50 +01:00
Andras Bacsai
f5cb2dbdcf Fix condition in removeServer method 2024-02-15 21:25:43 +01:00
Andras Bacsai
fe19769d82 fix: do not add the same server twice 2024-02-15 21:22:59 +01:00
Andras Bacsai
45e404b15b feat: disable gzip compression on service applications 2024-02-15 20:44:01 +01:00
Andras Bacsai
5bdaa68368 Add docker-registry service template and update service-templates.json 2024-02-15 15:39:27 +01:00
Andras Bacsai
d903a377bf Update validation and configuration titles 2024-02-15 14:14:11 +01:00
Andras Bacsai
8e7745f4c1 Remove unnecessary debug statement in Server.php 2024-02-15 13:54:18 +01:00
Andras Bacsai
a9ea6330d9 feat: revalidate server 2024-02-15 13:52:54 +01:00
Andras Bacsai
bfb0260550 fix: use ls / command instead ls 2024-02-15 13:52:42 +01:00
Andras Bacsai
bba1cb3832 fix: ec2 does not have uptime command lol
version++
2024-02-15 13:44:40 +01:00
Andras Bacsai
29ad2144b7 Merge pull request #1747 from coollabsio/next
v4.0.0-beta.218
2024-02-15 12:59:51 +01:00
Andras Bacsai
38d367e709 fix: padding left on input boxes 2024-02-15 12:59:25 +01:00
Andras Bacsai
0e81ff970f Merge pull request #1746 from coollabsio/next
v4.0.0-beta.217
2024-02-15 12:54:38 +01:00
Andras Bacsai
00feef40a3 Fix image tag in docker-compose.prod.yml 2024-02-15 12:29:30 +01:00
Andras Bacsai
dfba593072 feat: magic for traefik redirectregex in services 2024-02-15 12:08:48 +01:00
Andras Bacsai
c770c8d988 Add warning icon for configuration not applied 2024-02-15 12:04:52 +01:00
Andras Bacsai
0f071031a9 Refactor application FQDN handling 2024-02-15 12:01:59 +01:00
Andras Bacsai
99efa857f4 feat: add metabase
feat: consistent container names
fix: for services, you only need to add basicauth label, others are added by coolify
fix: label uuids are not randomly generated all the time
fix: changing force https will change the labels
2024-02-15 11:55:43 +01:00
Andras Bacsai
80035395ff Update version numbers + do not cleanup queue on cloud 2024-02-14 15:31:43 +01:00
Andras Bacsai
d3490e1c95 Merge pull request #1743 from coollabsio/next
v4.0.0-beta.216
2024-02-14 15:21:13 +01:00
Andras Bacsai
dab13c92eb Update disk_usage property type in ServerStatusJob 2024-02-14 15:21:03 +01:00
Andras Bacsai
1f18542960 fix: cleanup scheduled tasks 2024-02-14 15:14:06 +01:00
Andras Bacsai
8f21ea9367 Merge pull request #1727 from lxix/fix-scheduled-tasks
fix: Scheduled Tasks won't execute after deleting resource with scheduled task
2024-02-14 15:02:09 +01:00
Andras Bacsai
73e64d9052 fix: file volume creation
fix: network_mode host compose
2024-02-14 15:00:24 +01:00
Andras Bacsai
6cdd87da41 Update Directus image version to 10 2024-02-14 14:59:38 +01:00
Andras Bacsai
2a5d49f9b3 Merge pull request #1731 from notskamr/patch-1
Update directus-with-postgresql.yaml - Version bump
2024-02-14 14:58:43 +01:00
Andras Bacsai
cc7ba9eb9f Merge pull request #1734 from Geczy/fix-mg
fix: only add 'networks' key if 'network_mode' is absent
2024-02-14 14:31:48 +01:00
Andras Bacsai
c4cc42c8d5 Update version and release numbers 2024-02-14 10:35:44 +01:00
Andras Bacsai
2d0838b112 Merge pull request #1742 from coollabsio/next
v4.0.0-beta.215
2024-02-14 10:32:48 +01:00
Andras Bacsai
07b94a8e48 Update filebrowser.yaml and service-templates.json 2024-02-14 10:31:05 +01:00
Andras Bacsai
4b08abc144 Save storage on initial creation 2024-02-14 10:21:53 +01:00
Andras Bacsai
93e4fc2f32 Update filebrowser image and volume bindings 2024-02-14 10:14:14 +01:00
Andras Bacsai
6dd86eec30 Fix directory creation issue in LocalFileVolume.php and parseDockerComposeFile() 2024-02-14 10:13:49 +01:00
Andras Bacsai
a7ab5d55d3 Add syncthing data volumes and update syncthing service template 2024-02-14 10:00:27 +01:00
Andras Bacsai
689547463c Merge pull request #1712 from RayBB/syncthing-template
add Syncthing template
2024-02-14 09:50:50 +01:00
Andras Bacsai
8a0046c571 update packages + fix tests 2024-02-14 09:21:25 +01:00
Andras Bacsai
fb3991321a Update Coolify version and Sentry configuration 2024-02-14 08:53:13 +01:00
Andras Bacsai
ca6543a919 Merge pull request #1741 from coollabsio/next
v4.0.0-beta.214
2024-02-14 08:44:42 +01:00
Andras Bacsai
364a6aa3a2 fix: boolean docker options 2024-02-14 08:42:47 +01:00
Andras Bacsai
82b0667277 Update version numbers 2024-02-12 12:56:04 +01:00
Andras Bacsai
74c126c731 Merge pull request #1729 from coollabsio/next
v4.0.0-beta.213
2024-02-12 11:56:40 +01:00
Andras Bacsai
d87a0fe74f Refactor authentication check in Index.php 2024-02-12 11:53:28 +01:00
Andras Bacsai
9a858f628d Merge pull request #1732 from fipnooone/fix/previews-flex-wrap
Flex-wrap deployment previews
2024-02-12 11:50:41 +01:00
Andras Bacsai
fed01fa9d2 Fix subscription retrieval and handle missing subscriptions 2024-02-12 11:48:28 +01:00
Andras Bacsai
e1468da36a feat: add proxy start to server validation
fix: boarding flow updated
2024-02-12 11:46:36 +01:00
Andras Bacsai
ddfc1440cd fix: menu 2024-02-12 10:05:45 +01:00
Andras Bacsai
5fc46384e6 Refactor status component to exclude parentheses in status message 2024-02-11 18:08:36 +01:00
Andras Bacsai
48d9df1e43 Add conditional display of deployment server name in previews.blade.php 2024-02-11 17:29:14 +01:00
Andras Bacsai
6b62d91f82 Update service_name parameter to stack_service_uuid in index.blade.php 2024-02-11 17:24:20 +01:00
Matt
e6ca8cd167 fix: only add 'networks' key if 'network_mode' is absent 2024-02-11 09:22:09 -06:00
Andras Bacsai
059748ad3b fix: get service stack as uuid, not name 2024-02-11 15:44:02 +01:00
Andras Bacsai
a334f998a2 Add Bitnami Docker images for MariaDB, MongoDB, MySQL, PostgreSQL, and Redis 2024-02-11 15:40:02 +01:00
Andras Bacsai
53a5ccef31 fix: add docker compose check during server validation 2024-02-11 15:32:58 +01:00
Andras Bacsai
9eea73cefb Update Docker command in InstallLogDrain.php 2024-02-11 14:35:07 +01:00
Andras Bacsai
b210e1f243 fix: lock logdrain configuration when one of them are enabled 2024-02-11 14:31:21 +01:00
fipnooone
ef3202101c fix: flex wrap deployment previews 2024-02-10 13:40:35 +07:00
Varun Sahni
22d5159d16 Update directus-with-postgresql.yaml 2024-02-09 23:19:19 +05:30
Barnabás Schósz
1cbd30bd9e Fix Scheduled Tasks won't execute after deleting resource with scheduled task 2024-02-09 17:36:47 +01:00
Andras Bacsai
ad54358de7 Refactor resource index.blade.php file 2024-02-09 13:57:37 +01:00
Andras Bacsai
3689b58b92 Add success message for cleanup_queue 2024-02-09 13:51:31 +01:00
Andras Bacsai
5a4180a750 Update navbar dropdown menu styling 2024-02-09 13:50:19 +01:00
Andras Bacsai
047922b13a fix: new menu ui 2024-02-09 13:48:40 +01:00
Andras Bacsai
798d747164 add Docker run command parse test 2024-02-09 13:38:17 +01:00
Andras Bacsai
29676ffb22 Update Teams link in navbar.blade.php 2024-02-09 08:42:39 +01:00
Andras Bacsai
8a50b063d4 fix: user proper image_tag, if set 2024-02-08 15:22:07 +01:00
Andras Bacsai
3d2444ab2e Update version 4 to 4.0.0-beta.213 2024-02-08 14:54:30 +01:00
Andras Bacsai
7c395edab4 Fix conditional statement in navbar.blade.php 2024-02-08 14:06:43 +01:00
Andras Bacsai
7e7f322e21 Refactor admin authentication and routing***
***Add redirect for non-cloud users and instance admins without admin token.***

***Always include admin route, regardless of cloud status.
2024-02-08 14:01:16 +01:00
Andras Bacsai
9350fb4b97 Fix access control in Admin Index and hide Admin link in navbar 2024-02-08 13:54:16 +01:00
Andras Bacsai
59c3cc6ce1 Refactor admin authentication logic in Index component 2024-02-08 13:46:43 +01:00
Andras Bacsai
d7001937ac Fix access control in Admin Index and Navbar components 2024-02-08 13:40:26 +01:00
Andras Bacsai
3fe58ec66b Fix isInstanceAdmin function call in Index.php 2024-02-08 13:33:51 +01:00
Andras Bacsai
ed12f73483 Update admin authentication and version numbers 2024-02-08 13:33:34 +01:00
Andras Bacsai
6914280fb1 Merge pull request #1722 from coollabsio/next
v4.0.0-beta.212
2024-02-08 13:20:47 +01:00
Andras Bacsai
3dd5546369 Update destination.blade.php with server configurations 2024-02-08 13:19:11 +01:00
Andras Bacsai
576bff1af9 Remove exception and update server check in StopService 2024-02-08 13:17:08 +01:00
Andras Bacsai
3c4243d854 fix: go to prod env from dashboard if there is no other envs defined 2024-02-08 13:12:23 +01:00
Andras Bacsai
23d121d67a fix: make sure resources are deleted in async mode 2024-02-08 13:10:29 +01:00
Andras Bacsai
548304765c feat: cleanup queue 2024-02-08 12:47:00 +01:00
Andras Bacsai
037ba3ff79 Fix cleanup of halted deployments 2024-02-08 12:37:56 +01:00
Andras Bacsai
48b4c17391 Refactor init command to use full-cleanup option 2024-02-08 12:36:33 +01:00
Andras Bacsai
6acc0e6025 Add dynamic timeout for deployments 2024-02-08 12:34:01 +01:00
Andras Bacsai
43d7f746e4 Refactor destination.blade.php to include server and network information 2024-02-08 11:59:01 +01:00
Andras Bacsai
146fee14e5 Refactor destination.blade.php: Update server selection UI 2024-02-08 11:50:40 +01:00
Andras Bacsai
bde7fb2acb Fix user authentication condition in Index component 2024-02-08 11:46:23 +01:00
Andras Bacsai
08a729dc7b Add admin dashboard route and view 2024-02-08 11:45:19 +01:00
Andras Bacsai
7554de5993 Refactor app:init command and update cleanup options 2024-02-08 11:05:31 +01:00
Stuart Rowlands
0538c2f478 Added pre-deployment support. 2024-02-08 20:02:30 +10:00
Stuart Rowlands
77a0179822 Added basic support for post-deployment commands. 2024-02-08 19:27:43 +10:00
Andras Bacsai
3d7295fec3 fix: new menu on navbar 2024-02-08 09:08:21 +01:00
Andras Bacsai
fd814abd8a Update version numbers to 4.0.0-beta.212 2024-02-07 20:44:17 +01:00
Andras Bacsai
4c38a59995 Merge branch 'main' into next 2024-02-07 20:35:35 +01:00
Andras Bacsai
642a6e3203 Merge pull request #1721 from coollabsio/quick-fix
Update database/service start commands
2024-02-07 20:34:58 +01:00
Andras Bacsai
9edbc15828 Update database start commands 2024-02-07 20:34:13 +01:00
Andras Bacsai
43eb2fb00b new navbar 2024-02-07 15:31:03 +01:00
Andras Bacsai
9a899deeb8 Fix DNS validation and error handling 2024-02-07 14:59:33 +01:00
Andras Bacsai
9e1a7d5d9a feat: multi deployments 2024-02-07 14:55:06 +01:00
Andras Bacsai
5bdbab7276 ui: specific about newrelic logdrains 2024-02-07 09:04:35 +01:00
Andras Bacsai
13bceb934f Refactor Application model and migration 2024-02-06 17:37:07 +01:00
Andras Bacsai
78b194cb16 Refactor application status update logic and add complex_status column 2024-02-06 15:42:31 +01:00
Andras Bacsai
3616fc8ca9 Refactor code and add additional destinations 2024-02-06 15:05:11 +01:00
Andras Bacsai
10e307f92b Refactor help button in navbar and boarding layout 2024-02-06 11:50:03 +01:00
Andras Bacsai
01f027ac1b Update version numbers to 4.0.0-beta.211 2024-02-06 11:41:49 +01:00
Andras Bacsai
dadc7aaf08 Merge pull request #1718 from coollabsio/next
v4.0.0-beta.210
2024-02-06 11:41:17 +01:00
Andras Bacsai
b96807d34c fix: feedback from self-hosted envs to discord 2024-02-06 11:36:20 +01:00
Andras Bacsai
45b736bb01 fix: stripe webhooks 2024-02-06 11:11:26 +01:00
Andras Bacsai
3d873a79a0 Merge pull request #1715 from coollabsio/next
fix: deploy issue with tag deployment
2024-02-06 07:21:33 +01:00
Andras Bacsai
6869c582ff Update retrieval of applications and services in Deploy controller 2024-02-06 07:21:06 +01:00
Andras Bacsai
9b9e5e939c Fix resource not found error and improve mass deployment process 2024-02-06 07:19:11 +01:00
Andras Bacsai
f626c15ecc Update version numbers + fix deploy issue 2024-02-06 07:12:09 +01:00
Andras Bacsai
8df1fe2e60 Merge pull request #1714 from coollabsio/next
Refactor database and service start commands
2024-02-05 20:58:16 +01:00
Andras Bacsai
fd2a533057 Refactor database and service start commands 2024-02-05 20:57:40 +01:00
Andras Bacsai
0a6401f990 Merge pull request #1713 from coollabsio/next
v4.0.0-beta.208
2024-02-05 20:24:44 +01:00
Andras Bacsai
1326fcb345 Add count checks for MySQL and MariaDB in isEmpty() method 2024-02-05 20:15:02 +01:00
Andras Bacsai
8b8e534598 Update version numbers to 4.0.0-beta.208 2024-02-05 19:53:14 +01:00
Andras Bacsai
0b518a3b76 Refactor code to load tags for environment applications and databases 2024-02-05 19:52:06 +01:00
RayBB
f357f40fc7 add syncthing template 2024-02-05 16:45:46 +01:00
Andras Bacsai
93fb14884e Merge pull request #1711 from coollabsio/next
Refactor server validation and installation logic
2024-02-05 15:13:56 +01:00
Andras Bacsai
26ccc4afb4 Refactor server validation and installation logic 2024-02-05 15:13:39 +01:00
Andras Bacsai
5fda1bb932 Merge pull request #1710 from coollabsio/next
v4.0.0-beta.207
2024-02-05 14:57:21 +01:00
Andras Bacsai
409ba8a1bb Refactor application deployment logic 2024-02-05 14:47:06 +01:00
Andras Bacsai
49f5240ff8 fix: better server validation and installation process
fix: add destination to queue deployment
feat: force start deployment
2024-02-05 14:40:54 +01:00
Andras Bacsai
0c3ed3d393 Update BunnyCDN sync and version numbers 2024-02-05 10:17:40 +01:00
Andras Bacsai
6e3dc474f2 Merge pull request #1702 from coollabsio/next
v4.0.0-beta.206
2024-02-05 10:06:59 +01:00
Andras Bacsai
d3eb87561e Fix styling issue in tag links 2024-02-05 10:00:53 +01:00
Andras Bacsai
8b58c8f856 Add tags to show and index views 2024-02-05 09:51:44 +01:00
Andras Bacsai
8c60ef5bd6 Update link in error message to the correct documentation 2024-02-04 17:00:13 +01:00
Andras Bacsai
1d59383c78 feat: clone to env 2024-02-04 16:54:12 +01:00
Andras Bacsai
60f590454d Update application deployment status in job handling 2024-02-04 14:40:23 +01:00
Andras Bacsai
dcb61a553e Merge pull request #1706 from piscis/patch-1
fix: Wrap tags and avoid horizontal overflow
2024-02-04 14:39:55 +01:00
Andras Bacsai
e06e31642f Refactor modal component and add new functionality 2024-02-04 14:07:08 +01:00
Andras Bacsai
9dfce48380 Add private_keys array initialization and define additional private properties 2024-02-04 13:50:24 +01:00
Andras Bacsai
8eed87e2f7 Update main class with mx-auto 2024-02-04 13:50:16 +01:00
Alex
d56d4eb8fc fix: Wrap tags and avoid horizontal overflow 2024-02-04 13:15:39 +01:00
Andras Bacsai
fd32cd04ab Refactor invoice payment failure handling in webhooks.php 2024-02-04 12:23:00 +01:00
Andras Bacsai
1d3b7ffd3b Refactor tags functionality and improve user experience 2024-02-03 12:44:18 +01:00
Andras Bacsai
0b5baf60a5 fix: tags 2024-02-03 12:39:07 +01:00
Andras Bacsai
bc31df6fb2 Update version numbers to 4.0.0-beta.206 2024-02-02 14:52:24 +01:00
Andras Bacsai
818399bc23 Merge pull request #1700 from coollabsio/next
v4.0.0-beta.205
2024-02-02 12:41:46 +01:00
Andras Bacsai
e7fdff0f69 feat: tags
ui: improvements
2024-02-02 11:50:28 +01:00
Andras Bacsai
6312c0ba84 feat: tags and tag deploy webhooks 2024-02-01 15:38:12 +01:00
Andras Bacsai
44efe0b5e1 Update versions and fix code formatting 2024-02-01 11:59:20 +01:00
Andras Bacsai
de7d584648 Merge pull request #1694 from coollabsio/next
v4.0.0-beta.204
2024-02-01 10:54:27 +01:00
Andras Bacsai
b9f12d2586 fix: duplicate domain check 2024-02-01 10:53:05 +01:00
Andras Bacsai
c76e8bb0de fix: migrate to new modal 2024-01-31 16:14:12 +01:00
Andras Bacsai
3b655f8e3f Update filebrowser image tag to use filebrowser/filebrowser:s6 2024-01-31 15:16:06 +01:00
Andras Bacsai
2b9df41444 fix: create dynamic directory 2024-01-31 15:04:08 +01:00
Andras Bacsai
628fec6904 fix: sentry error 2024-01-31 14:22:48 +01:00
Andras Bacsai
f36135cbfc fix: sentry 2024-01-31 14:20:57 +01:00
Andras Bacsai
75fe005055 fix: sentry error 2024-01-31 14:19:45 +01:00
Andras Bacsai
8ff7aeb78b ui: new modal component 2024-01-31 14:18:59 +01:00
Andras Bacsai
f1a9e28d5a fix: sentry 2024-01-31 14:18:51 +01:00
Andras Bacsai
843cd90ee5 Update exception type in generate_github_installation_token function 2024-01-31 13:47:16 +01:00
Andras Bacsai
1cbfd03912 fix: sentry fix 2024-01-31 13:46:40 +01:00
Andras Bacsai
ce60a39dc5 Throw RuntimeException instead of Exception when no resource is found in ScheduledTaskJob 2024-01-31 13:45:58 +01:00
Andras Bacsai
f1e4395a83 Refactor shared variable type validation 2024-01-31 13:43:23 +01:00
Andras Bacsai
52fd7ad571 fix: not able to use other shared envs 2024-01-31 13:40:15 +01:00
Andras Bacsai
5f797ec0ae Update version and release numbers 2024-01-31 10:28:18 +01:00
Andras Bacsai
21e77bf0c1 Merge pull request #1691 from coollabsio/next
v4.0.0-beta.203
2024-01-31 10:02:04 +01:00
Andras Bacsai
0686e48e89 fix: service deletion 2024-01-31 09:58:41 +01:00
Andras Bacsai
1cef233db2 fix: regenerate labels on application clone 2024-01-31 09:03:54 +01:00
Andras Bacsai
907e52572c fix: validate server navbar upated 2024-01-31 08:56:49 +01:00
Andras Bacsai
795c8abf64 Refactor service and resource deletion logic 2024-01-30 17:38:07 +01:00
Andras Bacsai
cc641d8cba revert 2024-01-30 17:16:52 +01:00
Andras Bacsai
d4668ef44a refactor 2024-01-30 14:12:40 +01:00
Andras Bacsai
e8b539c3bd Refactor CleanupUnreachableServers command to update server IP address 2024-01-30 10:53:12 +01:00
Andras Bacsai
6555f0b50c Fix unreachable server cleanup and enable daily schedule 2024-01-30 10:52:57 +01:00
Andras Bacsai
bb05058dda feat: cleanup unreachable servers 2024-01-30 10:50:54 +01:00
Andras Bacsai
9667cd4a7a fix: handle duplicate error instead of sql error
fix: set fqdns to null if you delete an app or a serviceapp
fix: make stucked resources a separate command
2024-01-30 09:48:51 +01:00
Andras Bacsai
3ae9501814 fix: dns validation + duplicated fqdns 2024-01-30 09:22:34 +01:00
Andras Bacsai
73d0948734 fix: service deletion fix 2024-01-30 08:26:33 +01:00
Andras Bacsai
c3e2a741ea Update version numbers 2024-01-29 20:35:35 +01:00
Andras Bacsai
4792146f1d Merge pull request #1687 from coollabsio/next
v4.0.0-beta.202
2024-01-29 16:37:39 +01:00
Andras Bacsai
09b9305aa3 Refactor git_clone_command in generateGitImportCommands function 2024-01-29 16:33:06 +01:00
Andras Bacsai
ff7d0d442d Update button text in private key create view 2024-01-29 16:33:01 +01:00
Andras Bacsai
9a127bdc80 Refactor webhook handling logic to remove duplicate code and improve readability 2024-01-29 16:31:10 +01:00
Andras Bacsai
919e88afb4 Refactor docker run options to compose format 2024-01-29 16:21:23 +01:00
Andras Bacsai
1d1ec20cb7 Update version numbers 2024-01-29 16:13:04 +01:00
Andras Bacsai
5c29ecdf10 feat: add initial support for custom docker run commands 2024-01-29 16:07:00 +01:00
Andras Bacsai
9e09c449cf fix: service deletion function 2024-01-29 16:03:45 +01:00
Andras Bacsai
09bcd693f5 Merge pull request #1683 from coollabsio/next
v4.0.0-beta.201
2024-01-29 14:04:30 +01:00
Andras Bacsai
0c15e45419 Update WordPress database configuration 2024-01-29 14:03:59 +01:00
Andras Bacsai
31cbf552a2 Merge pull request #1677 from mraf/wordpress-template
fix: add env variables for wordpress template without database
2024-01-29 13:58:31 +01:00
Andras Bacsai
f7853ee174 Refactor deployments_per_server variable and update dashboard view
This commit refactors the `deployments_per_server` variable in the `Dashboard` class to remove the type hint and updates the corresponding view file to handle the changes. The `deployments_per_server` variable is now grouped by `server_name` and converted to an array. This improves the organization and readability of the code.
2024-01-29 13:26:50 +01:00
Andras Bacsai
de3a7b6eca Add previous page functionality to deployment index
This commit adds the functionality to navigate to the previous page in the deployment index. It includes changes to the `Index.php` and `index.blade.php` files.
2024-01-29 13:06:26 +01:00
Andras Bacsai
b56c7c34cb fix: unhealthy deployments should be failed 2024-01-29 12:51:20 +01:00
Andras Bacsai
49845f3da7 fix: webhooks for multiple apps 2024-01-29 11:23:04 +01:00
Andras Bacsai
987409bae4 fix: bitbucket manual deployments 2024-01-29 10:43:18 +01:00
Andras Bacsai
07d8461f96 Fix URL encoding in deployment and status notifications 2024-01-29 08:49:05 +01:00
Andras Bacsai
f255a71434 Merge pull request #1673 from Niki2k1/feat/bitbucket-manual-webhook
feat: added manual webhook support for bitbucket
2024-01-29 08:38:05 +01:00
Andras Bacsai
2a2818ac0d Merge pull request #1680 from iamEvanYT/discord-deployment-url-fix
fix: encode project name in discord webhook notifications
2024-01-29 08:32:57 +01:00
Andras Bacsai
fd3cdc2c7d Update deployment status border color 2024-01-29 08:32:04 +01:00
Andras Bacsai
84c3f832ae Add missing closing div tag in dashboard.blade.php 2024-01-29 08:30:00 +01:00
Andras Bacsai
70c28fceeb fix: change env variable length 2024-01-29 08:24:21 +01:00
iamEvan
f2c4f83f5a Fix 2024-01-27 20:33:22 +00:00
iamEvan
c8dd6f07ac Encode Project Name 2024-01-27 20:22:27 +00:00
Andras Bacsai
561e424a7d feat: dashboard live deployment view 2024-01-27 18:44:40 +01:00
Andras Bacsai
c46d38907e fix: queue 2024-01-27 17:18:13 +01:00
Andras Bacsai
5c334bbac6 feat: add PR comments 2024-01-26 18:46:50 +01:00
Andras Bacsai
9628072b0c Update dependencies in package.json and mix-manifest.json 2024-01-26 11:36:44 +01:00
Andras Bacsai
39ecff9f90 Update version numbers 2024-01-26 11:34:18 +01:00
Andras Bacsai
d1daec060a Merge pull request #1675 from coollabsio/next
v4.0.0-beta.200
2024-01-26 11:25:32 +01:00
Andras Bacsai
fb5bea7f91 Update Docker actions versions 2024-01-26 11:23:49 +01:00
Andras Bacsai
efc3ea6e40 Update Docker actions versions 2024-01-26 11:20:27 +01:00
Andras Bacsai
ecefb9e1f5 Update vite version to 4.5.2 2024-01-26 11:15:35 +01:00
Andras Bacsai
a4dea2009a Remove unused imports and routes 2024-01-26 11:13:02 +01:00
Andras Bacsai
829e41f93f Delete TeamSharedVariablesIndexTest.php 2024-01-26 11:12:07 +01:00
Andras Bacsai
e7e3adc7fb Fix condition for checking localhost key in ProductionSeeder.php 2024-01-26 11:11:41 +01:00
Andras Bacsai
a993fef235 Update Docker build command 2024-01-26 10:56:22 +01:00
Andras Bacsai
81df71416c Update Docker images and add pull_policy 2024-01-26 10:52:58 +01:00
Andras Bacsai
40af7aa025 Update Docker build command to include latest tag 2024-01-26 10:46:46 +01:00
Andras Bacsai
050155328b Update toast.blade.php to use x-html instead of x-text 2024-01-26 10:37:53 +01:00
Andras Bacsai
376c081bed Add .env file as read-only volume 2024-01-26 10:37:33 +01:00
Andras Bacsai
9f5e1fa9e3 Update docker-compose.windows.yml file 2024-01-26 10:34:48 +01:00
Andras Bacsai
7d139fd33b Add environment file for Windows Docker Desktop 2024-01-26 10:31:37 +01:00
Andras Bacsai
b75a2857a0 Update environment variables and Docker image for Windows Docker Desktop 2024-01-26 10:24:42 +01:00
Andras Bacsai
ab6c1ddc20 Update docker-compose.windows.yml with bind mount for .env file 2024-01-26 10:16:44 +01:00
Andras Bacsai
dc0b0980a9 Update coolify image and environment variables 2024-01-26 09:57:59 +01:00
Andras Bacsai
788d1711db Refactor SSH command generation in remoteProcess.php 2024-01-26 09:36:08 +01:00
Andras Bacsai
d92dc4c5e6 Update testing-host image and remove unnecessary build configuration 2024-01-26 09:03:59 +01:00
Andras Bacsai
4bf34aea62 Add Coolify Testing Host workflow 2024-01-26 08:59:32 +01:00
Andras Bacsai
7e9a54ce67 Fix SSH command generation and disable mux in validateConnection() 2024-01-26 08:54:56 +01:00
Andras Bacsai
f8c19e1fb3 Update contact links in error and subscription views 2024-01-26 08:39:54 +01:00
Andras Bacsai
27c1bda09b Update button labels in create forms 2024-01-25 16:02:31 +01:00
Andras Bacsai
1af7ffcdc4 Refactor input field for port number 2024-01-25 15:58:58 +01:00
Andras Bacsai
39647367a5 Add build pack selection and update related fields 2024-01-25 15:57:04 +01:00
Andras Bacsai
ae4b263810 Update GitHub App registration button 2024-01-25 15:26:23 +01:00
Andras Bacsai
8901bb5df8 Refactor deployment cancellation and queue management 2024-01-25 13:45:17 +01:00
mraf
053aa25d2c fix: add env variables for wordpress template without database 2024-01-25 12:27:41 +01:00
Andras Bacsai
7a7157c155 fix: deployment queue
fix: cancel deployment
ui: changed to simpler toaster
2024-01-25 11:57:47 +01:00
Andras Bacsai
0c5e8600bd Update build pack settings and port values 2024-01-25 08:59:11 +01:00
Andras Bacsai
048e153025 Remove unnecessary condition in setDestination method 2024-01-25 08:57:16 +01:00
Andras Bacsai
e7cafe6850 fix: restrict concurrent deployments per server 2024-01-25 08:36:47 +01:00
Andras Bacsai
1385a86084 Refactor team() method and update references to team() in get_real_environment_variables() method 2024-01-24 15:56:43 +01:00
Andras Bacsai
348923ae02 Refactor realValue() method to include resource lookup 2024-01-24 15:54:55 +01:00
Andras Bacsai
7d754558b0 Fix branch selection and handle missing service 2024-01-24 12:26:14 +01:00
Andras Bacsai
744609e7e9 fix sentry errors 2024-01-24 12:10:03 +01:00
Andras Bacsai
9bd05b65a3 Update build pack and make GithubApp nullable 2024-01-24 12:07:58 +01:00
Andras Bacsai
d42934f258 fix: sentry error 2024-01-24 11:57:51 +01:00
Andras Bacsai
ff752e2411 feat: able to deploy multiple resources with webhook 2024-01-24 11:49:40 +01:00
Andras Bacsai
4120fba9a8 Update default concurrent_builds value to 2 2024-01-24 11:48:00 +01:00
Andras Bacsai
6ecb9c21ce cloud: send notification email if payment 2024-01-24 11:28:01 +01:00
Andras Bacsai
01f7b07fa3 feat: concurrent builds / server 2024-01-24 11:12:23 +01:00
Niklas Lausch
54d8cb9027 feat: added manual webhook support for bitbucket 2024-01-24 10:56:24 +01:00
Andras Bacsai
238337fecb Add new shared variable and update variable usage 2024-01-23 20:26:45 +01:00
Andras Bacsai
7a51acbf8d add slide-over component 2024-01-23 19:01:17 +01:00
Andras Bacsai
fb478c79b3 feat: shared environments 2024-01-23 17:13:23 +01:00
Andras Bacsai
abcc004953 feat: clone any resource 2024-01-22 16:08:18 +01:00
Andras Bacsai
2edf71a0dd feat: move resources between projects / environments 2024-01-22 15:12:38 +01:00
Andras Bacsai
cbec39099a Refactor Git section in advanced.blade.php 2024-01-22 14:13:40 +01:00
Andras Bacsai
dba5499182 Update version numbers + update glitchtip 2024-01-22 12:00:44 +01:00
Andras Bacsai
2fdf52929c Merge pull request #1670 from coollabsio/next
v4.0.0-beta.199
2024-01-22 11:02:30 +01:00
Andras Bacsai
8128dfc061 Update resource limits helper links 2024-01-22 10:47:47 +01:00
Andras Bacsai
2b394d6fea fix: show container on logs/executecontainer command views
fix: exclude containers with restart: no from hc
feat: add compose to predefined docker network
service: add glitchtip
2024-01-21 14:30:03 +01:00
Andras Bacsai
964ded1d0b fix: redis custom conf 2024-01-21 12:06:51 +01:00
Andras Bacsai
838c3830d6 Merge pull request #1664 from coollabsio/next
v4.0.0-beta.198
2024-01-18 15:06:12 +01:00
Andras Bacsai
2db93bd9b9 Add echo statement for queue cleanup and update cleanup message 2024-01-18 14:56:12 +01:00
Andras Bacsai
2f82dedd4f Add call to 'cleanup:queue' command in Init.php and remove 'cleanup:queue' command from Kernel.php 2024-01-18 14:49:47 +01:00
Andras Bacsai
8106602d15 Add CleanupQueue command to clean up Redis queue keys 2024-01-18 14:47:17 +01:00
Andras Bacsai
3d0bf6b472 Update resource name in notification messages 2024-01-18 14:03:51 +01:00
Andras Bacsai
ba7a7e9695 Update server and version configurations 2024-01-18 13:33:57 +01:00
Andras Bacsai
dd0ad04384 Merge pull request #1662 from coollabsio/next
Do not report server is not ready
2024-01-18 12:45:05 +01:00
Andras Bacsai
910a1f43a9 Do not report server is not ready 2024-01-18 12:44:20 +01:00
Andras Bacsai
e2f959ce4c Merge pull request #1659 from coollabsio/next
v4.0.0-beta.197
2024-01-18 12:14:50 +01:00
Andras Bacsai
68fe886fb0 Add restart functionality to proxy deployment 2024-01-18 12:14:11 +01:00
Andras Bacsai
4631c73809 Update general.blade.php with build server option and network section 2024-01-18 12:05:48 +01:00
Andras Bacsai
77558b37da refactor build server 2024-01-18 11:40:13 +01:00
Andras Bacsai
e060409a76 fix: links 2024-01-18 11:24:07 +01:00
Andras Bacsai
af01bc3e77 fix: service deletion bug! 2024-01-17 15:48:01 +01:00
Andras Bacsai
1e158badfc Update button text in by-ip.blade.php 2024-01-17 15:41:38 +01:00
Andras Bacsai
c620bb58ed Add build pack selection and show/hide static site options 2024-01-17 15:41:32 +01:00
Andras Bacsai
8a91395472 Update server model to use 'coolify' instead of 'coolify-overlay' for the name field 2024-01-17 14:11:46 +01:00
Andras Bacsai
c5f3398b73 Update build server and swarm support messages 2024-01-17 14:06:41 +01:00
Andras Bacsai
3878527de8 Update server unreachable notifications to include automatic revival 2024-01-17 14:02:54 +01:00
Andras Bacsai
4abcb2d5b9 Update notification labels in Discord and Telegram settings 2024-01-17 12:23:58 +01:00
Andras Bacsai
a635e51486 fix: server status job 2024-01-17 11:52:56 +01:00
Andras Bacsai
b6ce2e9122 typo 2024-01-17 11:39:45 +01:00
Andras Bacsai
8c60dd5523 typo 2024-01-17 11:21:47 +01:00
Andras Bacsai
94e2d951c4 Revert commented out code and execute remote command to remove Docker container 2024-01-16 15:45:54 +01:00
Andras Bacsai
381e24bea5 fix: git pull command for deploy key based previews 2024-01-16 15:45:19 +01:00
Andras Bacsai
2b1e35980f empty nixpacks type result in error 2024-01-16 15:26:44 +01:00
Andras Bacsai
a42c8da344 fix: proxy ui view
feat: build server 🌮
2024-01-16 15:19:14 +01:00
Andras Bacsai
7a0e415ecf fix: checkbox click 2024-01-16 12:28:59 +01:00
Andras Bacsai
d721f4809a fix ui 2024-01-16 12:20:40 +01:00
Andras Bacsai
22431eee9a fix: change proxy view 2024-01-16 11:32:56 +01:00
Andras Bacsai
c058c0a766 Update release version to 4.0.0-beta.197 2024-01-16 08:38:39 +01:00
Andras Bacsai
1724c0d3ff Merge pull request #1658 from coollabsio/next
v4.0.0-beta.196
2024-01-15 20:22:57 +01:00
Andras Bacsai
0b8f48230f Remove unnecessary echo statements in Server.php 2024-01-15 20:22:13 +01:00
Andras Bacsai
e8d84b7067 Update server and version configurations 2024-01-15 19:57:29 +01:00
Andras Bacsai
5236bbc757 Merge pull request #1655 from coollabsio/next
v4.0.0-beta.195
2024-01-15 14:30:43 +01:00
Andras Bacsai
094e1d1bba Fix condition for merge_request actions in webhooks.php 2024-01-15 14:29:54 +01:00
Andras Bacsai
68b25523d6 Fix action condition in merge_request webhook handler 2024-01-15 14:27:45 +01:00
Andras Bacsai
bdc478d5f5 Update daisyui version in package.json and package-lock.json 2024-01-15 13:46:38 +01:00
Andras Bacsai
002472d7c6 Update version numbers and dependencies 2024-01-15 13:40:32 +01:00
Andras Bacsai
0d65bf62b9 Merge pull request #1654 from coollabsio/next
v4.0.0-beta.194
2024-01-15 13:24:01 +01:00
Andras Bacsai
01c7e76071 only add gzip + https redirect once 2024-01-15 13:23:28 +01:00
656 changed files with 24199 additions and 10373 deletions

View File

@@ -0,0 +1,12 @@
IS_WINDOWS_DOCKER_DESKTOP=true
APP_ID=coolify-windows-docker-desktop
APP_NAME=Coolify
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
DB_PASSWORD=coolify
REDIS_PASSWORD=coolify
PUSHER_APP_ID=coolify
PUSHER_APP_KEY=coolify
PUSHER_APP_SECRET=coolify

View File

@@ -0,0 +1,84 @@
name: Coolify Testing Host (v4-non-prod)
on:
push:
branches: [ "main", "next" ]
paths:
- .github/workflows/coolify-testing-host.yml
- docker/testing-host/Dockerfile
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-testing-host"
jobs:
amd64:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v3
with:
no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v3
with:
no-cache: true
context: .
file: docker/testing-host/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}

View File

@@ -15,15 +15,15 @@ jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -36,15 +36,15 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build image and push to registry
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -59,13 +59,13 @@ jobs:
needs: [amd64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}

View File

@@ -12,9 +12,9 @@ jobs:
amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -24,7 +24,7 @@ jobs:
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -34,9 +34,9 @@ jobs:
aarch64:
runs-on: [self-hosted, arm64]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -46,7 +46,7 @@ jobs:
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
@@ -61,13 +61,13 @@ jobs:
needs: [amd64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -78,7 +78,7 @@ jobs:
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -1,22 +0,0 @@
<?php
use App\Models\User;
$email = 'test@example.com';
$user = User::whereEmail($email)->first();
$teams = $user->teams;
foreach ($teams as $team) {
$servers = $team->servers;
if ($servers->count() > 0) {
foreach ($servers as $server) {
dump($server);
$server->delete();
}
}
dump($team);
$team->delete();
}
if ($user) {
dump($user);
$user->delete();
}

View File

@@ -1,28 +0,0 @@
<?php
/**
* @label Send Email
* @description Send email to all users
*/
use App\Models\User;
use Illuminate\Support\Facades\Mail;
set_transanctional_email_settings();
$users = User::whereEmail('test@example.com');
foreach ($users as $user) {
Mail::send([], [], function ($message) use ($user) {
$message
->to($user->email)
->subject("Testing")
->text(
<<<EOF
Hello,
Welcome to Coolify Cloud.
Here is your user id: $user->id
EOF
);
});
}

View File

@@ -10,6 +10,17 @@ No vendor lock-in, which means that all the configuration for your applications/
For more information, take a look at our landing page [here](https://coolify.io).
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us [here](https://coolify.io/docs/contact).
# Donations
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
@@ -22,8 +33,11 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
## Github Sponsors ($15+)
## Github Sponsors ($40+)
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
@@ -66,16 +80,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
- Better support
- Less maintenance for you
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us [here](https://coolify.io/docs/contact).
# Recognitions
@@ -93,6 +97,10 @@ Contact us [here](https://coolify.io/docs/contact).
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
# Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image")
# Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

View File

@@ -3,6 +3,8 @@
namespace App\Actions\Application;
use App\Models\Application;
use App\Models\StandaloneDocker;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
@@ -10,10 +12,20 @@ class StopApplication
use AsAction;
public function handle(Application $application)
{
$server = $application->destination->server;
if ($server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
} else {
if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
return;
}
$servers = collect([]);
$servers->push($application->destination->server);
$application->additional_servers->map(function ($server) use ($servers) {
$servers->push($server);
});
foreach ($servers as $server) {
if (!$server->isFunctional()) {
return 'Server is not functional';
}
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($containers->count() > 0) {
foreach ($containers as $container) {
@@ -25,20 +37,7 @@ class StopApplication
);
}
}
// TODO: make notification for application
// $application->environment->project->team->notify(new StatusChanged($application));
}
// Delete Preview Deployments
$previewDeployments = $application->previews;
foreach ($previewDeployments as $previewDeployment) {
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Actions\Application;
use App\Models\Application;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplicationOneServer
{
use AsAction;
public function handle(Application $application, Server $server)
{
if ($application->destination->server->isSwarm()) {
return;
}
if (!$server->isFunctional()) {
return 'Server is not functional';
}
try {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
}
}
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}
}

View File

@@ -39,7 +39,7 @@ class PrepareCoolifyTask
public function __invoke(): Activity
{
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish);
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
dispatch($job);
$this->activity->refresh();
return $this->activity;

View File

@@ -21,6 +21,8 @@ class RunRemoteProcess
public $call_event_on_finish = null;
public $call_event_data = null;
protected $time_start;
protected $current_time;
@@ -34,7 +36,7 @@ class RunRemoteProcess
/**
* Create a new job instance.
*/
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
{
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
@@ -45,6 +47,7 @@ class RunRemoteProcess
$this->hide_from_output = $hide_from_output;
$this->ignore_errors = $ignore_errors;
$this->call_event_on_finish = $call_event_on_finish;
$this->call_event_data = $call_event_data;
}
public static function decodeOutput(?Activity $activity = null): string
@@ -111,9 +114,15 @@ class RunRemoteProcess
}
if ($this->call_event_on_finish) {
try {
event(resolve("App\\Events\\$this->call_event_on_finish", [
'userId' => $this->activity->causer_id,
]));
if ($this->call_event_data) {
event(resolve("App\\Events\\$this->call_event_on_finish", [
"data" => $this->call_event_data,
]));
} else {
event(resolve("App\\Events\\$this->call_event_on_finish", [
'userId' => $this->activity->causer_id,
]));
}
} catch (\Throwable $e) {
ray($e);
}

View File

@@ -25,7 +25,8 @@ class StartDatabaseProxy
$proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$databaseType = $database->databaseType();
$network = data_get($database, 'service.destination.network');
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
$network = $database->service->uuid;
$server = data_get($database, 'service.destination.server');
$proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) {
@@ -124,6 +125,7 @@ class StartDatabaseProxy
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",

View File

@@ -106,7 +106,7 @@ class StartMariadb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -140,7 +140,7 @@ class StartMariadb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -122,7 +122,7 @@ class StartMongodb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -156,7 +156,7 @@ class StartMongodb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {

View File

@@ -106,7 +106,7 @@ class StartMysql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -140,7 +140,7 @@ class StartMysql
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -128,7 +128,7 @@ class StartPostgresql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -164,7 +164,7 @@ class StartPostgresql
ray('Generate Environment Variables')->green();
ray($this->database->runtime_environment_variables)->green();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Database;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -106,7 +107,7 @@ class StartRedis
'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
@@ -116,7 +117,7 @@ class StartRedis
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -150,7 +151,7 @@ class StartRedis
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
@@ -165,8 +166,9 @@ class StartRedis
return;
}
$filename = 'redis.conf';
$content = $this->database->redis_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
Storage::disk('local')->put("tmp/redis.conf_{$this->database->uuid}", $this->database->redis_conf);
$path = Storage::path("tmp/redis.conf_{$this->database->uuid}");
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
Storage::disk('local')->delete("tmp/redis.conf_{$this->database->uuid}");
}
}

View File

@@ -17,6 +17,9 @@ class StopDatabase
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
{
$server = $database->destination->server;
if (!$server->isFunctional()) {
return 'Server is not functional';
}
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server

View File

@@ -17,10 +17,12 @@ class StopDatabaseProxy
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{
$server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$uuid = $database->service->uuid;
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
}

View File

@@ -11,7 +11,12 @@ class CheckConfiguration
use AsAction;
public function handle(Server $server, bool $reset = false)
{
$proxy_path = get_proxy_path();
$proxyType = $server->proxyType();
if ($proxyType === 'NONE') {
return 'OK';
}
$proxy_path = $server->proxyPath();
$proxy_configuration = instant_remote_process([
"mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml",

View File

@@ -10,6 +10,9 @@ class CheckProxy
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if ($server->proxyType() === 'NONE') {
return false;
}
if (!$server->isProxyShouldRun()) {
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
@@ -43,14 +46,14 @@ class CheckProxy
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}

View File

@@ -15,7 +15,7 @@ class SaveConfiguration
if (is_null($proxy_settings)) {
$proxy_settings = CheckConfiguration::run($server, true);
}
$proxy_path = get_proxy_path();
$proxy_path = $server->proxyPath();
$docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Proxy;
use App\Events\ProxyStarted;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,10 +14,12 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity
{
try {
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE') {
return 'OK';
}
$commands = collect([]);
$proxy_path = get_proxy_path();
$proxy_path = $server->proxyPath();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
@@ -27,15 +30,17 @@ class StartProxy
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'"
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
@@ -49,13 +54,14 @@ class StartProxy
}
if ($async) {
$activity = remote_process($commands, $server);
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
ProxyStarted::dispatch($server);
return 'OK';
}
} catch (\Throwable $e) {

View File

@@ -43,6 +43,7 @@ class InstallDocker
"echo 'Restarting Docker Engine...'",
"ls -l /tmp"
]);
return remote_process($command, $server);
} else {
if ($supported_os_type->contains('debian')) {
$command = $command->merge([
@@ -89,7 +90,6 @@ class InstallDocker
"echo 'Done!'",
]);
}
return remote_process($command, $server);
}
}

View File

@@ -128,9 +128,9 @@ class InstallLogDrain
if ($type !== 'custom') {
$parsers = base64_encode("
[PARSER]
Name empty_line_skipper
Format regex
Regex /^(?!\s*$).+/
Name empty_line_skipper
Format regex
Regex /^(?!\s*$).+/
");
}
$compose = base64_encode("
@@ -198,7 +198,7 @@ Files:
}
$restart_command = [
"echo 'Stopping old Fluent Bit'",
"cd $config_path && docker rm -f coolify-log-drain || true",
"cd $config_path && docker compose down --remove-orphans || true",
"echo 'Starting Fluent Bit'",
"cd $config_path && docker compose up -d --remove-orphans",
];

View File

@@ -25,7 +25,6 @@ class UpdateCoolify
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
if ($settings->next_channel) {
ray('next channel enabled');
$this->latestVersion = 'next';
@@ -44,7 +43,7 @@ class UpdateCoolify
}
$this->update();
}
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());

View File

@@ -10,33 +10,45 @@ class DeleteService
use AsAction;
public function handle(Service $service)
{
StopService::run($service);
$server = data_get($service, 'server');
$storagesToDelete = collect([]);
try {
$server = data_get($service, 'server');
if ($server->isFunctional()) {
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
$commands = [];
foreach ($service->applications()->get() as $application) {
$storages = $application->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
$service->environment_variables()->delete();
$commands = [];
foreach ($service->applications()->get() as $application) {
$storages = $application->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
}
foreach ($service->databases()->get() as $database) {
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
}
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
$commands[] = "docker rm -f $service->uuid";
instant_remote_process($commands, $server, false);
}
$application->forceDelete();
}
foreach ($service->databases()->get() as $database) {
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
} finally {
foreach ($service->applications()->get() as $application) {
$application->forceDelete();
}
$database->forceDelete();
foreach ($service->databases()->get() as $database) {
$database->forceDelete();
}
foreach ($service->scheduled_tasks as $task) {
$task->delete();
}
$service->tags()->detach();
}
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
$commands[] = "docker rm -f $service->uuid";
instant_remote_process($commands, $server, false);
$service->forceDelete();
}
}

View File

@@ -12,22 +12,24 @@ class StartService
public function handle(Service $service)
{
ray('Starting service: ' . $service->name);
$network = $service->destination->network;
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo 'Creating Docker network.'";
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
$commands[] = "echo 'Starting service $service->name on {$service->server->name}.'";
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
$commands[] = "echo Starting service.";
$commands[] = "echo 'Pulling images.'";
$commands[] = "docker compose pull";
$commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
$compose = data_get($service, 'docker_compose', []);
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []);
$network = $service->destination->network;
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
}
}
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity;

View File

@@ -10,20 +10,31 @@ class StopService
use AsAction;
public function handle(Service $service)
{
ray('Stopping service: ' . $service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
$application->update(['status' => 'exited']);
try {
$server = $service->destination->server;
if (!$server->isFunctional()) {
return 'Server is not functional';
}
ray('Stopping service: ' . $service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
} catch (\Exception $e) {
echo $e->getMessage();
ray($e->getMessage());
return $e->getMessage();
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Actions\Shared;
use App\Models\Application;
use Lorisleiva\Actions\Concerns\AsAction;
class ComplexStatusCheck
{
use AsAction;
public function handle(Application $application)
{
$servers = $application->additional_servers;
$servers->push($application->destination->server);
foreach ($servers as $server) {
$is_main_server = $application->destination->server->id === $server->id;
if (!$server->isFunctional()) {
if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']);
continue;
} else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue;
}
}
$container = instant_remote_process(["docker container inspect $(docker container ls -q --filter 'label=coolify.applicationId={$application->id}' --filter 'label=coolify.pullRequestId=0') --format '{{json .}}'"], $server, false);
$container = format_docker_command_output_to_json($container);
if ($container->count() === 1) {
$container = $container->first();
$containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
if ($is_main_server) {
$statusFromDb = $application->status;
if ($statusFromDb !== $containerStatus) {
$application->update(['status' => "$containerStatus:$containerHealth"]);
}
} else {
$additional_server = $application->additional_servers()->wherePivot('server_id', $server->id);
$statusFromDb = $additional_server->first()->pivot->status;
if ($statusFromDb !== $containerStatus) {
$additional_server->updateExistingPivot($server->id, ['status' => "$containerStatus:$containerHealth"]);
}
}
} else {
if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']);
continue;
} else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue;
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Console\Commands;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
public function handle()
{
$team_id = $this->option('team-id');
$servers = \App\Models\Server::where('team_id', $team_id)->get();
foreach ($servers as $server) {
$deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get();
foreach ($deployments as $deployment) {
$deployment->update(['status' => 'failed']);
instant_remote_process(['docker rm -f ' . $deployment->deployment_uuid], $server, false);
}
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command
{
protected $signature = 'cleanup:database {--yes}';
protected $description = 'Cleanup database';
public function handle()
{
if ($this->option('yes')) {
echo "Running database cleanup...\n";
} else {
echo "Running database cleanup in dry-run mode...\n";
}
$keep_days = 60;
echo "Keep days: $keep_days\n";
// Cleanup failed jobs table
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7));
$count = $failed_jobs->count();
echo "Delete $count entries from failed_jobs.\n";
if ($this->option('yes')) {
$failed_jobs->delete();
}
// Cleanup sessions table
$sessions = DB::table('sessions')->where('last_activity', '<', now()->subDays($keep_days)->timestamp);
$count = $sessions->count();
echo "Delete $count entries from sessions.\n";
if ($this->option('yes')) {
$sessions->delete();
}
// Cleanup activity_log table
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
$count = $activity_log->count();
echo "Delete $count entries from activity_log.\n";
if ($this->option('yes')) {
$activity_log->delete();
}
// Cleanup application_deployment_queues table
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
$count = $application_deployment_queues->count();
echo "Delete $count entries from application_deployment_queues.\n";
if ($this->option('yes')) {
$application_deployment_queues->delete();
}
// Cleanup webhooks table
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
$count = $webhooks->count();
echo "Delete $count entries from webhooks.\n";
if ($this->option('yes')) {
$webhooks->delete();
}
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
class CleanupQueue extends Command
{
protected $signature = 'cleanup:queue';
protected $description = 'Cleanup Queue';
public function handle()
{
echo "Running queue cleanup...\n";
$prefix = config('database.redis.options.prefix');
$keys = Redis::connection()->keys('*:laravel*');
foreach ($keys as $key) {
$keyWithoutPrefix = str_replace($prefix, '', $key);
Redis::connection()->del($keyWithoutPrefix);
}
}
}

View File

@@ -0,0 +1,308 @@
<?php
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ScheduledTask;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Console\Command;
class CleanupStuckedResources extends Command
{
protected $signature = 'cleanup:stucked-resources';
protected $description = 'Cleanup Stucked Resources';
public function handle()
{
ray('Running cleanup stucked resources.');
echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources();
}
private function cleanup_stucked_resources()
{
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
echo "Deleting stuck application: {$application->name}\n";
$application->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($postgresqls as $postgresql) {
echo "Deleting stuck postgresql: {$postgresql->name}\n";
$postgresql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($redis as $redis) {
echo "Deleting stuck redis: {$redis->name}\n";
$redis->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mongodbs as $mongodb) {
echo "Deleting stuck mongodb: {$mongodb->name}\n";
$mongodb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mysqls as $mysql) {
echo "Deleting stuck mysql: {$mysql->name}\n";
$mysql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mariadbs as $mariadb) {
echo "Deleting stuck mariadb: {$mariadb->name}\n";
$mariadb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($services as $service) {
echo "Deleting stuck service: {$service->name}\n";
$service->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
}
try {
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceApps as $serviceApp) {
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
$serviceApp->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
try {
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceDbs as $serviceDb) {
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
$serviceDb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
try {
$scheduled_tasks = ScheduledTask::all();
foreach ($scheduled_tasks as $scheduled_task) {
if (!$scheduled_task->service && !$scheduled_task->application) {
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
$scheduled_task->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
}
// Cleanup any resources that are not attached to any environment or destination or server
try {
$applications = Application::all();
foreach ($applications as $application) {
if (!data_get($application, 'environment')) {
echo 'Application without environment: ' . $application->name . '\n';
$application->forceDelete();
continue;
}
if (!$application->destination()) {
echo 'Application without destination: ' . $application->name . '\n';
$application->forceDelete();
continue;
}
if (!data_get($application, 'destination.server')) {
echo 'Application without server: ' . $application->name . '\n';
$application->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment: ' . $postgresql->name . '\n';
$postgresql->forceDelete();
continue;
}
if (!$postgresql->destination()) {
echo 'Postgresql without destination: ' . $postgresql->name . '\n';
$postgresql->forceDelete();
continue;
}
if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server: ' . $postgresql->name . '\n';
$postgresql->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::all();
foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) {
echo 'Redis without environment: ' . $redis->name . '\n';
$redis->forceDelete();
continue;
}
if (!$redis->destination()) {
echo 'Redis without destination: ' . $redis->name . '\n';
$redis->forceDelete();
continue;
}
if (!data_get($redis, 'destination.server')) {
echo 'Redis without server: ' . $redis->name . '\n';
$redis->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment: ' . $mongodb->name . '\n';
$mongodb->forceDelete();
continue;
}
if (!$mongodb->destination()) {
echo 'Mongodb without destination: ' . $mongodb->name . '\n';
$mongodb->forceDelete();
continue;
}
if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server: ' . $mongodb->name . '\n';
$mongodb->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment: ' . $mysql->name . '\n';
$mysql->forceDelete();
continue;
}
if (!$mysql->destination()) {
echo 'Mysql without destination: ' . $mysql->name . '\n';
$mysql->forceDelete();
continue;
}
if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server: ' . $mysql->name . '\n';
$mysql->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment: ' . $mariadb->name . '\n';
$mariadb->forceDelete();
continue;
}
if (!$mariadb->destination()) {
echo 'Mariadb without destination: ' . $mariadb->name . '\n';
$mariadb->forceDelete();
continue;
}
if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server: ' . $mariadb->name . '\n';
$mariadb->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::all();
foreach ($services as $service) {
if (!data_get($service, 'environment')) {
echo 'Service without environment: ' . $service->name . '\n';
$service->forceDelete();
continue;
}
if (!$service->destination()) {
echo 'Service without destination: ' . $service->name . '\n';
$service->forceDelete();
continue;
}
if (!data_get($service, 'server')) {
echo 'Service without server: ' . $service->name . '\n';
$service->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in service: {$e->getMessage()}\n";
}
try {
$serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceApplication without service: ' . $service->name . '\n';
$service->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in serviceApplications: {$e->getMessage()}\n";
}
try {
$serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service: ' . $service->name . '\n';
$service->forceDelete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class CleanupUnreachableServers extends Command
{
protected $signature = 'cleanup:unreachable-servers';
protected $description = 'Cleanup Unreachable Servers (7 days)';
public function handle()
{
echo "Running unreachable server cleanup...\n";
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(7))->get();
if ($servers->count() > 0) {
foreach ($servers as $server) {
echo "Cleanup unreachable server ($server->id) with name $server->name";
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([
'ip' => '1.2.3.4'
]);
}
}
}
}

View File

@@ -2,29 +2,28 @@
namespace App\Console\Commands;
use App\Jobs\DatabaseBackupStatusJob;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess;
use App\Notifications\Database\DailyBackup;
use App\Notifications\Test;
use App\Notifications\TransactionalEmails\InvitationLink;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Illuminate\Support\Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
@@ -58,6 +57,8 @@ class Emails extends Command
options: [
'updates' => 'Send Update Email to all users',
'emails-test' => 'Test',
'database-backup-statuses-daily' => 'Database - Backup Statuses (Daily)',
'application-deployment-success-daily' => 'Application - Deployment Success (Daily)',
'application-deployment-success' => 'Application - Deployment Success',
'application-deployment-failed' => 'Application - Deployment Failed',
'application-status-changed' => 'Application - Status Changed',
@@ -71,8 +72,12 @@ class Emails extends Command
],
);
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to');
if (isDev()) {
$this->email = "test@example.com";
} else {
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to:');
}
}
set_transanctional_email_settings();
@@ -106,7 +111,7 @@ class Emails extends Command
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
'token' => encrypt($email),
]);
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
$this->mail->view('emails.updates', ["unsubscribeUrl" => $unsubscribeUrl]);
$this->sendEmail($email);
}
}
@@ -115,6 +120,35 @@ class Emails extends Command
$this->mail = (new Test())->toMail();
$this->sendEmail();
break;
case 'database-backup-statuses-daily':
$scheduled_backups = ScheduledDatabaseBackup::all();
$databases = collect();
foreach ($scheduled_backups as $scheduled_backup) {
$last_days_backups = $scheduled_backup->get_last_days_backup_status();
if ($last_days_backups->isEmpty()) {
continue;
}
$failed = $last_days_backups->where('status', 'failed');
$database = $scheduled_backup->database;
$databases->put($database->name, [
'failed_count' => $failed->count(),
]);
}
$this->mail = (new DailyBackup($databases))->toMail();
$this->sendEmail();
break;
case 'application-deployment-success-daily':
$applications = Application::all();
foreach ($applications as $application) {
$deployments = $application->get_last_days_deployments();
ray($deployments);
if ($deployments->isEmpty()) {
continue;
}
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
$this->sendEmail();
}
break;
case 'application-deployment-success':
$application = Application::all()->first();
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Horizon extends Command
{
protected $signature = 'start:horizon';
protected $description = 'Start Horizon';
public function handle()
{
if (config('coolify.is_horizon_enabled')) {
$this->info('Horizon is enabled. Starting.');
$this->call('horizon');
exit(0);
} else {
exit(0);
}
}
}

View File

@@ -4,56 +4,55 @@ namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class Init extends Command
{
protected $signature = 'app:init {--cleanup}';
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $description = 'Cleanup instance related stuffs';
public function handle()
{
$this->alive();
$cleanup = $this->option('cleanup');
if ($cleanup) {
echo "Running cleanup\n";
$this->cleanup_stucked_resources();
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments();
return;
}
if ($full_cleanup) {
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
// $this->cleanup_ssh();
}
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
try {
setup_dynamic_configuration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
try {
$server = Server::find(0)->first();
$server->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
return;
}
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources');
}
private function restore_coolify_db_backup()
{
@@ -127,8 +126,13 @@ class Init extends Command
// Cleanup any failed deployments
try {
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
foreach ($halted_deployments as $deployment) {
if (isCloud()) {
return;
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
ray($deployment->id, $deployment->status);
echo "Cleaning up deployment: {$deployment->id}\n";
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
}
@@ -136,273 +140,4 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_resources()
{
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
echo "Deleting stuck application: {$application->name}\n";
$application->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($postgresqls as $postgresql) {
echo "Deleting stuck postgresql: {$postgresql->name}\n";
$postgresql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($redis as $redis) {
echo "Deleting stuck redis: {$redis->name}\n";
$redis->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mongodbs as $mongodb) {
echo "Deleting stuck mongodb: {$mongodb->name}\n";
$mongodb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mysqls as $mysql) {
echo "Deleting stuck mysql: {$mysql->name}\n";
$mysql->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($mariadbs as $mariadb) {
echo "Deleting stuck mariadb: {$mariadb->name}\n";
$mariadb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($services as $service) {
echo "Deleting stuck service: {$service->name}\n";
$service->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
}
try {
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceApps as $serviceApp) {
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
$serviceApp->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
try {
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($serviceDbs as $serviceDb) {
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
$serviceDb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
// Cleanup any resources that are not attached to any environment or destination or server
try {
$applications = Application::all();
foreach ($applications as $application) {
if (!data_get($application, 'environment')) {
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
$application->delete();
continue;
}
if (!$application->destination()) {
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
$application->delete();
continue;
}
if (!data_get($application, 'destination.server')) {
echo 'Application without server: ' . $application->name . ' soft deleting\n';
$application->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete();
continue;
}
if (!$postgresql->destination()) {
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete();
continue;
}
if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::all();
foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) {
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
$redis->delete();
continue;
}
if (!$redis->destination()) {
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
$redis->delete();
continue;
}
if (!data_get($redis, 'destination.server')) {
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
$redis->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete();
continue;
}
if (!$mongodb->destination()) {
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete();
continue;
}
if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
$mysql->delete();
continue;
}
if (!$mysql->destination()) {
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
$mysql->delete();
continue;
}
if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
$mysql->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete();
continue;
}
if (!$mariadb->destination()) {
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete();
continue;
}
if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::all();
foreach ($services as $service) {
if (!data_get($service, 'environment')) {
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
$service->delete();
continue;
}
if (!$service->destination()) {
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
$service->delete();
continue;
}
if (!data_get($service, 'server')) {
echo 'Service without server: ' . $service->name . ' soft deleting\n';
$service->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in service: {$e->getMessage()}\n";
}
try {
$serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
$service->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in serviceApplications: {$e->getMessage()}\n";
}
try {
$serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
$service->delete();
continue;
}
}
} catch (\Throwable $e) {
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Scheduler extends Command
{
protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler';
public function handle()
{
if (config('coolify.is_scheduler_enabled')) {
$this->info('Scheduler is enabled. Starting.');
$this->call('schedule:work');
exit(0);
} else {
exit(0);
}
}
}

View File

@@ -73,6 +73,18 @@ class ServicesGenerate extends Command
} else {
$slogan = str($file)->headline()->value();
}
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
if ($logo->count() > 0) {
$logo = str($logo[0])->after('# logo:')->trim()->value();
} else {
$logo = 'svgs/unknown.svg';
}
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
if ($minversion->count() > 0) {
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
} else {
$minversion = '0.0.0';
}
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
if ($env_file->count() > 0) {
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
@@ -88,6 +100,12 @@ class ServicesGenerate extends Command
} else {
$tags = null;
}
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
if ($port->count() > 0) {
$port = str($port[0])->after('# port:')->trim()->value();
} else {
$port = null;
}
$json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2));
$payload = [
@@ -96,7 +114,12 @@ class ServicesGenerate extends Command
'slogan' => $slogan,
'compose' => $yaml,
'tags' => $tags,
'logo' => $logo,
'minversion' => $minversion,
];
if ($port) {
$payload['port'] = $port;
}
if ($env_file) {
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
$env_file_base64 = base64_encode($env_file_content);

View File

@@ -48,7 +48,7 @@ class SyncBunny extends Command
$versions = "versions.json";
PendingRequest::macro('storage', function ($fileName) use($that) {
PendingRequest::macro('storage', function ($fileName) use ($that) {
$headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json',
@@ -76,23 +76,26 @@ class SyncBunny extends Command
}
if ($only_template) {
$this->info('About to sync service-templates.json to BunnyCDN.');
}
if ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.');
}
$confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) {
return;
}
if ($only_template) {
$confirmed = confirm("Are you sure you want to sync?");
if (!$confirmed) {
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]);
$this->info('Service template uploaded & purged...');
return;
}
if ($only_version) {
} else if ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.');
$file = file_get_contents("$parent_dir/$versions");
$json = json_decode($file, true);
$actual_version = data_get($json, 'coolify.v4.version');
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
if (!$confirmed) {
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
@@ -101,6 +104,7 @@ class SyncBunny extends Command
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),

View File

@@ -36,6 +36,8 @@ class Kernel extends ConsoleKernel
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
@@ -45,6 +47,10 @@ class Kernel extends ConsoleKernel
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
if (!isCloud()) {
$schedule->command('cleanup:database --yes')->daily();
}
}
}
private function pull_helper_image($schedule)
@@ -60,10 +66,10 @@ class Kernel extends ConsoleKernel
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers;
$servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else {
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
}
foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
@@ -72,8 +78,37 @@ class Kernel extends ConsoleKernel
}
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyFiveMinutes()->onOneServer();
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
// Delayed Jobs
// foreach ($containerServers as $server) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new ContainerStatusJob($server);
// $job->delay($randomSeconds);
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
// dispatch($job);
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
// if ($server->isLogDrainEnabled()) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new CheckLogDrainContainerJob($server);
// $job->delay($randomSeconds);
// dispatch($job);
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
// }
// }
// foreach ($servers as $server) {
// $schedule
// ->call(function () use ($server) {
// $randomSeconds = rand(1, 40);
// $job = new ServerStatusJob($server);
// $job->delay($randomSeconds);
// dispatch($job);
// })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer();
// }
}
private function instance_auto_update($schedule)
{
@@ -89,7 +124,6 @@ class Kernel extends ConsoleKernel
{
$scheduled_backups = ScheduledDatabaseBackup::all();
if ($scheduled_backups->isEmpty()) {
ray('no scheduled backups');
return;
}
foreach ($scheduled_backups as $scheduled_backup) {
@@ -111,15 +145,15 @@ class Kernel extends ConsoleKernel
}
}
private function check_scheduled_tasks($schedule) {
private function check_scheduled_tasks($schedule)
{
$scheduled_tasks = ScheduledTask::all();
if ($scheduled_tasks->isEmpty()) {
ray('no scheduled tasks');
return;
}
foreach ($scheduled_tasks as $scheduled_task) {
$service = $scheduled_task->service()->get();
$application = $scheduled_task->application()->get();
$service = $scheduled_task->service;
$application = $scheduled_task->application;
if (!$application && !$service) {
ray('application/service attached to scheduled task does not exist');
@@ -134,7 +168,6 @@ class Kernel extends ConsoleKernel
task: $scheduled_task
))->cron($scheduled_task->frequency)->onOneServer();
}
}
protected function commands(): void

View File

@@ -21,6 +21,7 @@ class CoolifyTaskArgs extends Data
public ?string $status = null ,
public bool $ignore_errors = false,
public $call_event_on_finish = null,
public $call_event_data = null
) {
if(is_null($status)){
$this->status = ProcessStatus::QUEUED->value;

View File

@@ -10,4 +10,5 @@ enum ProcessStatus: string
case ERROR = 'error';
case KILLED = 'killed';
case CANCELLED = 'cancelled';
case CLOSED = 'closed';
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProxyStarted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public $data)
{
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProxyStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception("Team id is null");
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@@ -77,6 +77,9 @@ class Handler extends ExceptionHandler
);
}
);
if (str($e->getMessage())->contains('No space left on device')) {
return;
}
ray('reporting to sentry');
Integration::captureUnhandledException($e);
});

View File

@@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService;
use App\Http\Controllers\Controller;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\Tag;
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Deploy extends Controller
{
public function deployments(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->toArray();
return response()->json($deployments_per_server, 200);
}
public function deploy(Request $request)
{
$teamId = get_team_id_from_token();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
if (is_null($teamId)) {
return invalid_token();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
} else if ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
{
$uuids = explode(',', $uuid);
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$deployments = collect();
$payload = collect();
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
}
}
}
if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray());
return response()->json($payload->toArray(), 200);
}
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
{
$tags = explode(',', $tags);
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$message = collect([]);
$deployments = collect();
$payload = collect();
foreach ($tags as $tag) {
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
if (!$found_tag) {
// $message->push("Tag {$tag} not found.");
continue;
}
$applications = $found_tag->applications()->get();
$services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($applications as $resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
}
$message = $message->merge($return_message);
}
foreach ($services as $resource) {
['message' => $return_message] = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
ray($message);
if ($message->count() > 0) {
$payload->put('message', $message->toArray());
if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray());
}
return response()->json($payload->toArray(), 200);
}
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): array
{
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
$type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') {
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,
force_rebuild: $force,
);
$message = "Application {$resource->name} deployment queued.";
} else if ($type === 'App\Models\StandalonePostgresql') {
StartPostgresql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneRedis') {
StartRedis::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMongodb') {
StartMongodb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMysql') {
StartMysql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMariadb') {
StartMariadb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\Service') {
StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\InstanceSettings;
use App\Models\Project as ModelsProject;
use Illuminate\Http\Request;
class Domains extends Controller
{
public function domains(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$projects = ModelsProject::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
$settings = InstanceSettings::get();
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
});
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
$domains->push([
'domain' => $fqdn,
'ip' => $settings->public_ipv4,
]);
}
if ($settings->public_ipv6) {
$domains->push([
'domain' => $fqdn,
'ip' => $settings->public_ipv6,
]);
}
if (!$settings->public_ipv4 && !$settings->public_ipv6) {
$domains->push([
'domain' => $fqdn,
'ip' => $ip,
]);
}
} else {
$domains->push([
'domain' => $fqdn,
'ip' => $ip,
]);
}
}
}
$services = $projects->pluck('services')->flatten();
if ($services->count() > 0) {
foreach ($services as $service) {
$service_applications = $service->applications;
if ($service_applications->count() > 0) {
foreach ($service_applications as $application) {
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
});
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
$domains->push([
'domain' => $fqdn,
'ip' => $settings->public_ipv4,
]);
}
if ($settings->public_ipv6) {
$domains->push([
'domain' => $fqdn,
'ip' => $settings->public_ipv6,
]);
}
if (!$settings->public_ipv4 && !$settings->public_ipv6) {
$domains->push([
'domain' => $fqdn,
'ip' => $ip,
]);
}
} else {
$domains->push([
'domain' => $fqdn,
'ip' => $ip,
]);
}
}
}
}
}
$domains = $domains->groupBy('ip')->map(function ($domain) {
return $domain->pluck('domain')->flatten();
})->map(function ($domain, $ip) {
return [
'ip' => $ip,
'domains' => $domain,
];
})->values();
return response()->json($domains);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project as ModelsProject;
use Illuminate\Http\Request;
class Project extends Controller
{
public function projects(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json($projects);
}
public function project_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
return response()->json($project);
}
public function environment_details(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json($environment);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project;
use Illuminate\Http\Request;
class Resources extends Controller
{
public function resources(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$projects = Project::where('team_id', $teamId)->get();
$resources = collect();
$resources->push($projects->pluck('applications')->flatten());
$resources->push($projects->pluck('services')->flatten());
foreach (collect(DATABASE_TYPES) as $db) {
$resources->push($projects->pluck(str($db)->plural(2))->flatten());
}
$resources = $resources->flatten();
$resources = $resources->map(function ($resource) {
$payload = $resource->toArray();
if ($resource->getMorphClass() === 'App\Models\Service') {
$payload['status'] = $resource->status();
} else {
$payload['status'] = $resource->status;
}
$payload['type'] = $resource->type();
return $payload;
});
return response()->json($resources);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
class Server extends Controller
{
public function servers(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable;
$server['is_usable'] = $server->settings->is_usable;
return $server;
});
return response()->json($servers);
}
public function server_by_uuid(Request $request)
{
$with_resources = $request->query('resources');
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
if (is_null($server)) {
return response()->json(['error' => 'Server not found.'], 404);
}
if ($with_resources) {
$server['resources'] = $server->definedResources()->map(function ($resource) {
$payload = [
'id' => $resource->id,
'uuid' => $resource->uuid,
'name' => $resource->name,
'type' => $resource->type(),
'created_at' => $resource->created_at,
'updated_at' => $resource->updated_at,
];
if ($resource->type() === 'service') {
$payload['status'] = $resource->status();
} else {
$payload['status'] = $resource->status;
}
return $payload;
});
} else {
$server->load(['settings']);
}
return response()->json($server);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class Team extends Controller
{
public function teams(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
return response()->json($teams);
}
public function team_by_id(Request $request)
{
$id = $request->id;
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id"], 404);
}
return response()->json($team);
}
public function members_by_id(Request $request)
{
$id = $request->id;
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id-members"], 404);
}
return response()->json($team->members);
}
public function current_team(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team);
}
public function current_team_members(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team->members);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class OauthController extends Controller {
public function redirect(string $provider)
{
$socialite_provider = get_socialite_provider($provider);
return $socialite_provider->redirect();
}
public function callback(string $provider)
{
try {
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (!$user) {
$user = User::create([
'name' => $oauthUser->name,
'email' => $oauthUser->email,
]);
}
Auth::login($user);
return redirect('/');
} catch (\Exception $e) {
ray($e->getMessage());
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
}
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Livewire\Project\Service\Storage;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Bitbucket extends Controller
{
public function manual(Request $request)
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Bitbicket::manual_bitbucket", $json);
return;
}
$return_payloads = collect([]);
$payload = $request->collect();
$headers = $request->headers->all();
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', "");
$x_bitbucket_event = data_get($headers, 'x-event-key.0', "");
$handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
if (!$handled_events->contains($x_bitbucket_event)) {
return response([
'status' => 'failed',
'message' => 'Nothing to do. Event not handled.',
]);
}
if ($x_bitbucket_event === 'repo:push') {
$branch = data_get($payload, 'push.changes.0.new.name');
$full_name = data_get($payload, 'repository.full_name');
if (!$branch) {
return response([
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
}
ray('Manual webhook bitbucket push event with branch: ' . $branch);
}
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
$branch = data_get($payload, 'pullrequest.destination.branch.name');
$base_branch = data_get($payload, 'pullrequest.source.branch.name');
$full_name = data_get($payload, 'repository.full_name');
$pull_request_id = data_get($payload, 'pullrequest.id');
$pull_request_html_url = data_get($payload, 'pullrequest.links.html.href');
$commit = data_get($payload, 'pullrequest.source.commit.hash');
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response([
'status' => 'failed',
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
]);
}
foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket');
$payload = $request->getContent();
list($algo, $hash) = explode('=', $x_bitbucket_token, 2);
$payloadHash = hash_hmac($algo, $payload, $webhook_secret);
if (!hash_equals($hash, $payloadHash) && !isDev()) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
]);
ray('Invalid signature');
continue;
}
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
]);
ray('Server is not functional: ' . $application->destination->server->name);
continue;
}
if ($x_bitbucket_event === 'repo:push') {
if ($application->isDeployable()) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Auto deployment disabled.',
]);
}
}
if ($x_bitbucket_event === 'pullrequest:created') {
if ($application->isPRDeployable()) {
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'bitbucket',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: $commit,
is_webhook: true,
git_type: 'bitbucket'
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled.',
]);
}
}
if ($x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
ray('Pull request rejected');
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment closed.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No preview deployment found.',
]);
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e);
return handleError($e);
}
}
}

View File

@@ -0,0 +1,459 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Enums\ProcessStatus;
use App\Http\Controllers\Controller;
use App\Jobs\ApplicationPullRequestUpdateJob;
use App\Jobs\GithubAppPermissionJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\GithubApp;
use App\Models\PrivateKey;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Github extends Controller
{
public function manual(Request $request)
{
try {
ray($request);
$return_payloads = collect([]);
$x_github_delivery = request()->header('X-GitHub-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
return Str::contains($file, $x_github_delivery);
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::manual_{$x_github_delivery}", $json);
return;
}
$x_github_event = Str::lower($request->header('X-GitHub-Event'));
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
$content_type = $request->header('Content-Type');
$payload = $request->collect();
if ($x_github_event === 'ping') {
// Just pong
return response('pong');
}
if ($content_type !== 'application/json') {
$payload = json_decode(data_get($payload, 'payload'), true);
}
if ($x_github_event === 'push') {
$branch = data_get($payload, 'ref');
$full_name = data_get($payload, 'repository.full_name');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
ray('Manual Webhook GitHub Push Event with branch: ' . $branch);
}
if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action');
$full_name = data_get($payload, 'repository.full_name');
$pull_request_id = data_get($payload, 'number');
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
}
if (!$branch) {
return response('Nothing to do. No branch found in the request.');
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_github_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
}
}
if ($x_github_event === 'pull_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_github');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) {
ray('Invalid signature');
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
]);
continue;
}
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_github_event === 'push') {
if ($application->isDeployable()) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true,
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled.',
]);
}
}
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'github',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true,
git_type: 'github'
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled.',
]);
}
}
if ($action === 'closed') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment closed.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No preview deployment found.',
]);
}
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
public function normal(Request $request)
{
try {
$return_payloads = collect([]);
$id = null;
$x_github_delivery = $request->header('X-GitHub-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
return Str::contains($file, $x_github_delivery);
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::normal_{$x_github_delivery}", $json);
return;
}
$x_github_event = Str::lower($request->header('X-GitHub-Event'));
$x_github_hook_installation_target_id = $request->header('X-GitHub-Hook-Installation-Target-Id');
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
$payload = $request->collect();
if ($x_github_event === 'ping') {
// Just pong
return response('pong');
}
$github_app = GithubApp::where('app_id', $x_github_hook_installation_target_id)->first();
if (is_null($github_app)) {
return response('Nothing to do. No GitHub App found.');
}
$webhook_secret = data_get($github_app, 'webhook_secret');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (config('app.env') !== 'local') {
if (!hash_equals($x_hub_signature_256, $hmac)) {
return response('Invalid signature.');
}
}
if ($x_github_event === 'installation' || $x_github_event === 'installation_repositories') {
// Installation handled by setup redirect url. Repositories queried on-demand.
$action = data_get($payload, 'action');
if ($action === 'new_permissions_accepted') {
GithubAppPermissionJob::dispatch($github_app);
}
return response('cool');
}
if ($x_github_event === 'push') {
$id = data_get($payload, 'repository.id');
$branch = data_get($payload, 'ref');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch);
}
if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action');
$id = data_get($payload, 'repository.id');
$pull_request_id = data_get($payload, 'number');
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
}
if (!$id || !$branch) {
return response('Nothing to do. No id or branch found.');
}
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
if ($x_github_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$branch'.");
}
}
if ($x_github_event === 'pull_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_github_event === 'push') {
if ($application->isDeployable()) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled.',
]);
}
}
if ($x_github_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'github',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true,
git_type: 'github'
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled.',
]);
}
}
if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment closed.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No preview deployment found.',
]);
}
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
public function redirect(Request $request)
{
try {
$code = $request->get('code');
$state = $request->get('state');
$github_app = GithubApp::where('uuid', $state)->firstOrFail();
$api_url = data_get($github_app, 'api_url');
$data = Http::withBody(null)->accept('application/vnd.github+json')->post("$api_url/app-manifests/$code/conversions")->throw()->json();
$id = data_get($data, 'id');
$slug = data_get($data, 'slug');
$client_id = data_get($data, 'client_id');
$client_secret = data_get($data, 'client_secret');
$private_key = data_get($data, 'pem');
$webhook_secret = data_get($data, 'webhook_secret');
$private_key = PrivateKey::create([
'name' => $slug,
'private_key' => $private_key,
'team_id' => $github_app->team_id,
'is_git_related' => true,
]);
$github_app->name = $slug;
$github_app->app_id = $id;
$github_app->client_id = $client_id;
$github_app->client_secret = $client_secret;
$github_app->webhook_secret = $webhook_secret;
$github_app->private_key_id = $private_key->id;
$github_app->save();
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) {
return handleError($e);
}
}
public function install(Request $request)
{
try {
$installation_id = $request->get('installation_id');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json);
return;
}
$source = $request->get('source');
$setup_action = $request->get('setup_action');
$github_app = GithubApp::where('uuid', $source)->firstOrFail();
if ($setup_action === 'install') {
$github_app->installation_id = $installation_id;
$github_app->save();
}
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (Exception $e) {
return handleError($e);
}
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Gitlab extends Controller
{
public function manual(Request $request)
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json);
return;
}
$return_payloads = collect([]);
$payload = $request->collect();
$headers = $request->headers->all();
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
$x_gitlab_event = data_get($payload, 'object_kind');
if ($x_gitlab_event === 'push') {
$branch = data_get($payload, 'ref');
$full_name = data_get($payload, 'project.path_with_namespace');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
if (!$branch) {
$return_payloads->push([
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
return response($return_payloads);
}
ray('Manual Webhook GitLab Push Event with branch: ' . $branch);
}
if ($x_gitlab_event === 'merge_request') {
$action = data_get($payload, 'object_attributes.action');
$branch = data_get($payload, 'object_attributes.source_branch');
$base_branch = data_get($payload, 'object_attributes.target_branch');
$full_name = data_get($payload, 'project.path_with_namespace');
$pull_request_id = data_get($payload, 'object_attributes.iid');
$pull_request_html_url = data_get($payload, 'object_attributes.url');
if (!$branch) {
$return_payloads->push([
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
return response($return_payloads);
}
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_gitlab_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
$return_payloads->push([
'status' => 'failed',
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
]);
return response($return_payloads);
}
}
if ($x_gitlab_event === 'merge_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
$return_payloads->push([
'status' => 'failed',
'message' => "Nothing to do. No applications found with branch '$base_branch'.",
]);
return response($return_payloads);
}
}
foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
if ($webhook_secret !== $x_gitlab_token) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
]);
ray('Invalid signature');
continue;
}
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional',
]);
ray('Server is not functional: ' . $application->destination->server->name);
continue;
}
if ($x_gitlab_event === 'push') {
if ($application->isDeployable()) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true
);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Deployments disabled',
]);
ray('Deployments disabled for ' . $application->name);
}
}
if ($x_gitlab_event === 'merge_request') {
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'gitlab',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
is_webhook: true,
git_type: 'gitlab'
);
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview Deployment queued',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled',
]);
ray('Preview deployments disabled for ' . $application->name);
}
} else if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview Deployment closed',
]);
return response($return_payloads);
}
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No Preview Deployment found',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No action found. Contact us for debugging.',
]);
}
}
}
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Jobs\ServerLimitCheckJob;
use App\Jobs\SubscriptionInvoiceFailedJob;
use App\Jobs\SubscriptionTrialEndedJob;
use App\Jobs\SubscriptionTrialEndsSoonJob;
use App\Models\Subscription;
use App\Models\Team;
use App\Models\Webhook;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
class Stripe extends Controller
{
public function events(Request $request)
{
try {
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json);
return;
}
$webhookSecret = config('subscription.stripe_webhook_secret');
$signature = $request->header('Stripe-Signature');
$excludedPlans = config('subscription.stripe_excluded_plans');
$event = \Stripe\Webhook::constructEvent(
$request->getContent(),
$signature,
$webhookSecret
);
$webhook = Webhook::create([
'type' => 'stripe',
'payload' => $request->getContent()
]);
$type = data_get($event, 'type');
$data = data_get($event, 'data.object');
switch ($type) {
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
if (is_null($clientReferenceId)) {
send_internal_notification('Checkout session completed without client reference id.');
break;
}
$userId = Str::before($clientReferenceId, ':');
$teamId = Str::after($clientReferenceId, ':');
$subscriptionId = data_get($data, 'subscription');
$customerId = data_get($data, 'customer');
$team = Team::find($teamId);
$found = $team->members->where('id', $userId)->first();
if (!$found->isAdmin()) {
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
}
$subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) {
send_internal_notification('Old subscription activated for team: ' . $teamId);
$subscription->update([
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true,
]);
} else {
send_internal_notification('New subscription for team: ' . $teamId);
Subscription::create([
'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true,
]);
}
break;
case 'invoice.paid':
$customerId = data_get($data, 'customer');
$planId = data_get($data, 'lines.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
}
$subscription->update([
'stripe_invoice_paid' => true,
]);
break;
case 'invoice.payment_failed':
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) {
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: ' . $customerId);
return response('No subscription found in Coolify.');
}
$team = data_get($subscription, 'team');
if (!$team) {
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: ' . $customerId);
return response('No team found in Coolify.');
}
if (!$subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: ' . $customerId);
} else {
send_internal_notification('Invoice payment failed but already paid: ' . $customerId);
}
break;
case 'payment_intent.payment_failed':
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) {
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: ' . $customerId);
return response('No subscription found in Coolify.');
}
if ($subscription->stripe_invoice_paid) {
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: ' . $customerId);
return;
}
send_internal_notification('Subscription payment failed for customer: ' . $customerId);
break;
case 'customer.subscription.updated':
$customerId = data_get($data, 'customer');
$status = data_get($data, 'status');
$subscriptionId = data_get($data, 'items.data.0.subscription');
$planId = data_get($data, 'items.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.');
break;
}
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (!$subscription) {
Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (!$subscription) {
send_internal_notification('No subscription found for: ' . $customerId);
return response("No subscription found", 400);
}
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
$feedback = data_get($data, 'cancellation_details.feedback');
$comment = data_get($data, 'cancellation_details.comment');
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
if (str($lookup_key)->contains('ultimate')) {
$quantity = data_get($data, 'items.data.0.quantity', 10);
$team = data_get($subscription, 'team');
$team->update([
'custom_server_limit' => $quantity,
]);
ServerLimitCheckJob::dispatch($team);
}
$subscription->update([
'stripe_feedback' => $feedback,
'stripe_comment' => $comment,
'stripe_plan_id' => $planId,
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
]);
if ($status === 'paused' || $status === 'incomplete_expired') {
$subscription->update([
'stripe_invoice_paid' => false,
]);
send_internal_notification('Subscription paused or incomplete for customer: ' . $customerId);
}
// Trial ended but subscribed, reactive servers
if ($trialEndedAlready && $status === 'active') {
$team = data_get($subscription, 'team');
$team->trialEndedButSubscribed();
}
if ($feedback) {
$reason = "Cancellation feedback for {$customerId}: '" . $feedback . "'";
if ($comment) {
$reason .= ' with comment: \'' . $comment . "'";
}
send_internal_notification($reason);
}
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
if ($cancelAtPeriodEnd) {
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
} else {
send_internal_notification('customer.subscription.updated for customer: ' . $customerId);
}
}
break;
case 'customer.subscription.deleted':
// End subscription
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
$team->trialEnded();
$subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id' => null,
'stripe_cancel_at_period_end' => false,
'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => true,
]);
send_internal_notification('customer.subscription.deleted for customer: ' . $customerId);
break;
case 'customer.subscription.trial_will_end':
// Not used for now
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (!$team) {
throw new Exception('No team found for subscription: ' . $subscription->id);
}
SubscriptionTrialEndsSoonJob::dispatch($team);
break;
case 'customer.subscription.paused':
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (!$team) {
throw new Exception('No team found for subscription: ' . $subscription->id);
}
$team->trialEnded();
$subscription->update([
'stripe_trial_already_ended' => true,
'stripe_invoice_paid' => false,
]);
SubscriptionTrialEndedJob::dispatch($team);
send_internal_notification('Subscription paused for customer: ' . $customerId);
break;
default:
// Unhandled event type
}
} catch (Exception $e) {
if ($type !== 'payment_intent.payment_failed') {
send_internal_notification("Subscription webhook ($type) failed: " . $e->getMessage());
}
$webhook->update([
'status' => 'failed',
'failure_reason' => $e->getMessage(),
]);
return response($e->getMessage(), 400);
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\Waitlist as ModelsWaitlist;
use Exception;
use Illuminate\Http\Request;
class Waitlist extends Controller
{
public function confirm(Request $request)
{
$email = request()->get('email');
$confirmation_code = request()->get('confirmation_code');
ray($email, $confirmation_code);
try {
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found) {
if (!$found->verified) {
if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
$found->verified = true;
$found->save();
send_internal_notification('Waitlist confirmed: ' . $email);
return 'Thank you for confirming your email address. We will notify you when you are next in line.';
} else {
$found->delete();
send_internal_notification('Waitlist expired: ' . $email);
return 'Your confirmation code has expired. Please sign up again.';
}
}
}
return redirect()->route('dashboard');
} catch (Exception $e) {
send_internal_notification('Waitlist confirmation failed: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}
}
public function cancel(Request $request)
{
$email = request()->get('email');
$confirmation_code = request()->get('confirmation_code');
try {
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found && !$found->verified) {
$found->delete();
send_internal_notification('Waitlist cancelled: ' . $email);
return 'Your email address has been removed from the waitlist.';
}
return redirect()->route('dashboard');
} catch (Exception $e) {
send_internal_notification('Waitlist cancellation failed: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->route('dashboard');
}
}
}

View File

View File

@@ -12,12 +12,16 @@ class DecideWhatToDoWithUser
{
public function handle(Request $request, Closure $next): Response
{
if (auth()?->user()?->teams?->count() === 0) {
$currentTeam = auth()->user()?->recreate_personal_team();
refreshSession($currentTeam);
}
if(auth()?->user()?->currentTeam()){
refreshSession(auth()->user()->currentTeam());
}
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect()->route('boarding');
return redirect()->route('onboarding');
}
return $next($request);
}
@@ -39,12 +43,12 @@ class DecideWhatToDoWithUser
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect()->route('boarding');
return redirect()->route('onboarding');
}
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
return redirect(RouteServiceProvider::HOME);
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
if (isSubscriptionActive() && $request->routeIs('subscription.index')) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);

View File

@@ -12,6 +12,7 @@ class PreventRequestsDuringMaintenance extends Middleware
* @var array<int, string>
*/
protected $except = [
//
'webhooks/*',
'/api/health'
];
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,180 +0,0 @@
<?php
namespace App\Jobs;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Traits\ExecuteRemoteCommand;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;
use Throwable;
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
public $timeout = 3600;
public $tries = 1;
public static int $batch_counter = 0;
public Server $mainServer;
public $servers;
public string $basedir;
public string $workdir;
public string $deploymentUuid;
public int $pullRequestId = 0;
// Git related
public string $gitImportCommands;
public ?string $gitType = null;
public string $gitRepository;
public string $gitBranch;
public int $gitPort;
public string $gitFullRepoUrl;
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
{
$this->mainServer = data_get($this->application, 'destination.server');
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
$this->gitType = data_get($this->deployment, 'git_type');
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
}
public function handle()
{
try {
ray()->clearAll();
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
if ($this->application->dockerfile_target_build) {
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
}
// Get the git repository and port (custom port or default port)
[
'repository' => $this->gitRepository,
'port' => $this->gitPort
] = $this->application->customRepository();
// Get the git branch and git import commands
[
'commands' => $this->gitImportCommands,
'branch' => $this->gitBranch,
'fullRepoUrl' => $this->gitFullRepoUrl
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
$this->servers = $this->application->servers();
if ($this->deployment->restart_only) {
if ($this->application->build_pack === 'dockerimage') {
throw new \Exception('Restart only is not supported for docker image based deployments');
}
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
$this->servers->each(function ($server) {
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
$this->restartOnly($server);
});
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
} catch (Throwable $exception) {
$this->fail($exception);
} finally {
$this->servers->each(function ($server) {
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
$server->executeRemoteCommand(
commands: collect([])->push([
"command" => "docker rm -f {$this->deploymentUuid}",
"hidden" => true,
"ignoreErrors" => true,
]),
loggingModel: $this->deployment
);
});
}
}
public function restartOnly(Server $server)
{
$server->executeRemoteCommand(
commands: $this->application->prepareHelperImage($this->deploymentUuid),
loggingModel: $this->deployment
);
$privateKey = data_get($this->application, 'private_key.private_key', null);
$gitLsRemoteCommand = collect([]);
if ($privateKey) {
$privateKey = base64_decode($privateKey);
$gitLsRemoteCommand
->push([
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
])
->push([
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
])
->push([
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
])
->push([
"name" => "git_commit_sha",
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
"hidden" => true,
]);
} else {
$gitLsRemoteCommand->push([
"name" => "git_commit_sha",
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
"hidden" => true,
]);
}
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
$server->executeRemoteCommand(
commands: $gitLsRemoteCommand,
loggingModel: $this->deployment
);
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
[
'productionImageName' => $productionImageName
] = $this->application->generateImageNames($commit, $this->pullRequestId);
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
return;
}
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
}
public function failed(Throwable $exception): void
{
ray($exception);
$this->next(ApplicationDeploymentStatus::FAILED->value);
}
private function next(string $status)
{
// If the deployment is cancelled by the user, don't update the status
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
$this->deployment->update([
'status' => $status,
]);
}
queue_next_deployment($this->application, isNew: true);
}
}

View File

@@ -17,38 +17,37 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $build_logs_url;
public Application $application;
public ApplicationPreview $preview;
public string $body;
public function __construct(
public string $application_id,
public int $pull_request_id,
public string $deployment_uuid,
public string $status
public Application $application,
public ApplicationPreview $preview,
public ProcessStatus $status,
public ?string $deployment_uuid = null
) {
}
public function handle()
{
try {
$this->application = Application::findOrFail($this->application_id);
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
$this->body = "The preview deployment is in progress. 🟡\n\n";
if ($this->application->is_public_repository()) {
return;
}
if ($this->status === ProcessStatus::FINISHED->value) {
if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment();
return;
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
$this->body = "The preview deployment is in progress. 🟡\n\n";
} else if ($this->status === ProcessStatus::FINISHED) {
$this->body = "The preview deployment is ready. 🟢\n\n";
if ($this->preview->fqdn) {
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
}
}
if ($this->status === ProcessStatus::ERROR->value) {
} else if ($this->status === ProcessStatus::ERROR) {
$this->body = "The preview deployment failed. 🔴\n\n";
}
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
@@ -77,10 +76,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
private function create_comment()
{
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
'body' => $this->body,
]);
$this->preview->pull_request_issue_comment_id = $data['id'];
$this->preview->save();
}
private function delete_comment()
{
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
}
}

View File

@@ -5,8 +5,10 @@ namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Bus\Queueable;
@@ -27,6 +29,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
@@ -37,16 +42,26 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->uuid;
}
public function __construct(public Server $server)
{
$this->handle();
}
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
return 'Server is not reachable.';
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
if ($application->additional_servers->count() > 0) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$applications = $applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id);
});
try {
if ($this->server->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
@@ -85,7 +100,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
});
}
}
$applications = $this->server->applications();
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
@@ -136,36 +150,67 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
} else {
$uuid = data_get($labels, 'com.docker.compose.service');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
$database = $databases->where('uuid', $uuid)->first();
if ($uuid == 'postgresql') {
ray($database);
}
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
ray($database);
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
ray('asdffff');
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// 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;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
@@ -211,7 +256,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if ($exitedService->status === 'exited') {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
@@ -233,7 +278,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $applications->where('id', $applicationId)->first();
if ($application->status === 'exited') {
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
@@ -258,7 +303,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
$preview = $previews->where('id', $previewId)->first();
if ($preview->status === 'exited') {
if (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
@@ -283,7 +328,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
$database = $databases->where('id', $database)->first();
if ($database->status === 'exited') {
if (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);

View File

@@ -21,7 +21,8 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
public function __construct(
public Activity $activity,
public bool $ignore_errors = false,
public $call_event_on_finish = null
public $call_event_on_finish = null,
public $call_event_data = null
) {
}
@@ -33,7 +34,8 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
$remote_process = resolve(RunRemoteProcess::class, [
'activity' => $this->activity,
'ignore_errors' => $this->ignore_errors,
'call_event_on_finish' => $this->call_event_on_finish
'call_event_on_finish' => $this->call_event_on_finish,
'call_event_data' => $this->call_event_data
]);
$remote_process();

View File

@@ -51,6 +51,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
@@ -316,7 +319,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
try {
$url = $this->database->getDbUrl(useInternal: true);
$url = $this->database->get_db_url(useInternal: true);
if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Jobs;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\Team;
use App\Notifications\Database\DailyBackup;
use App\Notifications\Server\HighDiskUsage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class DatabaseBackupStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public function __construct()
{
}
public function handle()
{
// $teams = Team::all();
// foreach ($teams as $team) {
// $scheduled_backups = $team->scheduledDatabaseBackups()->get();
// if ($scheduled_backups->isEmpty()) {
// continue;
// }
// foreach ($scheduled_backups as $scheduled_backup) {
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
// if ($last_days_backups->isEmpty()) {
// continue;
// }
// $failed = $last_days_backups->where('status', 'failed');
// }
// }
// $scheduled_backups = ScheduledDatabaseBackup::all();
// $databases = collect();
// $teams = collect();
// foreach ($scheduled_backups as $scheduled_backup) {
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
// if ($last_days_backups->isEmpty()) {
// continue;
// }
// $failed = $last_days_backups->where('status', 'failed');
// $database = $scheduled_backup->database;
// $team = $database->team();
// $teams->put($team->id, $team);
// $databases->put("{$team->id}:{$database->name}", [
// 'failed_count' => $failed->count(),
// ]);
// }
// foreach ($databases as $name => $database) {
// [$team_id, $name] = explode(':', $name);
// $team = $teams->get($team_id);
// $team?->notify(new DailyBackup($databases));
// }
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Actions\Application\StopApplication;
use App\Actions\Database\StopDatabase;
use App\Actions\Service\DeleteService;
use App\Actions\Service\StopService;
use App\Models\Application;
use App\Models\Service;
use App\Models\StandaloneMariadb;
@@ -18,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
{
@@ -30,40 +32,29 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
try {
$server = $this->resource->destination->server;
if (!$server->isFunctional()) {
$this->resource->forceDelete();
return 'Server is not functional';
}
$this->resource->delete();
$this->resource->forceDelete();
switch ($this->resource->type()) {
case 'application':
StopApplication::run($this->resource);
break;
case 'standalone-postgresql':
StopDatabase::run($this->resource);
break;
case 'standalone-redis':
StopDatabase::run($this->resource);
break;
case 'standalone-mongodb':
StopDatabase::run($this->resource);
break;
case 'standalone-mysql':
StopDatabase::run($this->resource);
break;
case 'standalone-mariadb':
StopDatabase::run($this->resource);
break;
}
if ($this->resource->type() === 'service') {
DeleteService::dispatch($this->resource);
} else {
$this->resource->forceDelete();
case 'service':
StopService::run($this->resource);
DeleteService::run($this->resource);
break;
}
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
throw $e;
} finally {
Artisan::queue('cleanup:stucked-resources');
}
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Jobs;
use App\Actions\Server\CleanupDocker;
use App\Models\Server;
use App\Notifications\Server\DockerCleanup;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -47,8 +48,9 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
CleanupDocker::run($this->server);
$usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
} else {
Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name);

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\GithubApp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public GithubApp $github_app)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->github_app->uuid))];
}
public function uniqueId(): int
{
return $this->github_app->uuid;
}
public function handle()
{
try {
$github_access_token = generate_github_jwt_token($this->github_app);
$response = Http::withHeaders([
'Authorization' => "Bearer $github_access_token",
'Accept' => 'application/vnd.github+json'
])->get("{$this->github_app->api_url}/app");
$response = $response->json();
$permissions = data_get($response, 'permissions');
$this->github_app->contents = data_get($permissions, 'contents');
$this->github_app->metadata = data_get($permissions, 'metadata');
$this->github_app->pull_requests = data_get($permissions, 'pull_requests');
$this->github_app->administration = data_get($permissions, 'administration');
$this->github_app->save();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
} catch (\Throwable $e) {
send_internal_notification('GithubAppPermissionJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -19,7 +19,7 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string

View File

@@ -38,6 +38,8 @@ class ScheduledTaskJob implements ShouldQueue
$this->resource = $service;
} else if ($application = $task->application()->first()) {
$this->resource = $application;
} else {
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
}
$this->team = Team::find($task->team_id);
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Jobs;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase $service)
{
}
public function handle()
{
$this->service->getFilesFromServer(isInit: true);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use App\Models\Team;
use App\Notifications\Server\ForceDisabled;
use App\Notifications\Server\ForceEnabled;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ServerLimitCheckJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Team $team)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->team->uuid))];
}
public function uniqueId(): int
{
return $this->team->uuid;
}
public function handle()
{
try {
$servers = $this->team->servers;
$servers_count = $servers->count();
$limit = $this->team->limits['serverLimit'];
$number_of_servers_to_disable = $servers_count - $limit;
ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable);
if ($number_of_servers_to_disable > 0) {
ray('Disabling servers');
$servers = $servers->sortbyDesc('created_at');
$servers_to_disable = $servers->take($number_of_servers_to_disable);
$servers_to_disable->each(function ($server) {
$server->forceDisableServer();
$this->team->notify(new ForceDisabled($server));
});
} else if ($number_of_servers_to_disable === 0) {
$servers->each(function ($server) {
if ($server->isForceDisabled()) {
$server->forceEnableServer();
$this->team->notify(new ForceEnabled($server));
}
});
}
} catch (\Throwable $e) {
send_internal_notification('ServerLimitCheckJob failed with: ' . $e->getMessage());
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?int $disk_usage = null;
public int|string|null $disk_usage = null;
public $tries = 4;
public function backoff(): int
{
@@ -37,6 +37,9 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
if (!$this->server->isServerReady($this->tries)) {
throw new \RuntimeException('Server is not ready.');
};
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Jobs;
use App\Models\LocalFileVolume;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerStorageSaveJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public LocalFileVolume $localFileVolume)
{
}
public function handle()
{
$this->localFileVolume->saveStorageOnServer();
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Listeners;
use Illuminate\Foundation\Events\MaintenanceModeDisabled as EventsMaintenanceModeDisabled;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class MaintenanceModeDisabledNotification
{
public function __construct()
{
}
public function handle(EventsMaintenanceModeDisabled $event): void
{
ray('Maintenance mode disabled!');
$files = Storage::disk('webhooks-during-maintenance')->files();
$files = collect($files);
$files = $files->sort();
foreach ($files as $file) {
$content = Storage::disk('webhooks-during-maintenance')->get($file);
$data = json_decode($content, true);
$symfonyRequest = new SymfonyRequest(
$data['query'],
$data['request'],
$data['attributes'],
$data['cookies'],
$data['files'],
$data['server'],
$data['content']
);
foreach ($data['headers'] as $key => $value) {
$symfonyRequest->headers->set($key, $value);
}
$request = Request::createFromBase($symfonyRequest);
$endpoint = str($file)->after('_')->beforeLast('_')->value();
$class = "App\Http\Controllers\Webhook\\" . ucfirst(str($endpoint)->before('::')->value());
$method = str($endpoint)->after('::')->value();
try {
$instance = new $class();
$instance->$method($request);
} catch (\Throwable $th) {
ray($th);
} finally {
Storage::disk('webhooks-during-maintenance')->delete($file);
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Listeners;
use App\Events\MaintenanceModeEnabled;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\MaintenanceModeEnabled as EventsMaintenanceModeEnabled;
use Illuminate\Queue\InteractsWithQueue;
class MaintenanceModeEnabledNotification
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(EventsMaintenanceModeEnabled $event): void
{
ray('Maintenance mode enabled!');
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Listeners;
use App\Events\ProxyStarted;
use App\Models\Server;
class ProxyStartedNotification
{
public Server $server;
public function __construct()
{
}
public function handle(ProxyStarted $event): void
{
$this->server = data_get($event, 'data');
$this->server->setupDefault404Redirect();
$this->server->setupDynamicProxyConfiguration();
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Livewire;
use App\Enums\ProcessStatus;
use App\Models\User;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
@@ -10,14 +11,17 @@ class ActivityMonitor extends Component
{
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
public bool $showWaiting = false;
protected $activity;
protected $listeners = ['newMonitorActivity'];
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
public function newMonitorActivity($activityId)
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->hydrateActivity();
@@ -35,13 +39,28 @@ class ActivityMonitor extends Component
// $this->setStatus(ProcessStatus::IN_PROGRESS);
$exit_code = data_get($this->activity, 'properties.exitCode');
if ($exit_code !== null) {
if ($exit_code === 0) {
// $this->setStatus(ProcessStatus::FINISHED);
} else {
// $this->setStatus(ProcessStatus::ERROR);
}
// if ($exit_code === 0) {
// // $this->setStatus(ProcessStatus::FINISHED);
// } else {
// // $this->setStatus(ProcessStatus::ERROR);
// }
$this->isPollingActive = false;
$this->dispatch('activityFinished');
if ($exit_code === 0) {
if ($this->eventToDispatch !== null) {
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
$causer_id = data_get($this->activity, 'causer_id');
$user = User::find($causer_id);
if ($user) {
foreach($user->teams as $team) {
$teamId = $team->id;
$this->eventToDispatch::dispatch($teamId);
}
}
return;
}
$this->dispatch($this->eventToDispatch);
}
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Livewire\Admin;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class Index extends Component
{
public $users = [];
public function mount()
{
if (!isCloud()) {
return redirect()->route('dashboard');
}
if (auth()->user()->id !== 0) {
return redirect()->route('dashboard');
}
$this->users = User::whereHas('teams', function ($query) {
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
})->get()->filter(function ($user) {
return $user->id !== 0;
});
}
public function switchUser(int $user_id)
{
if (auth()->user()->id !== 0) {
return redirect()->route('dashboard');
}
$user = User::find($user_id);
$team_to_switch_to = $user->teams->first();
Cache::forget("team:{$user->id}");
auth()->login($user);
refreshSession($team_to_switch_to);
return redirect(request()->header('Referer'));
}
public function render()
{
return view('livewire.admin.index');
}
}

View File

@@ -2,20 +2,24 @@
namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component;
class Index extends Component
{
protected $listeners = ['serverInstalled' => 'validateServer'];
public string $currentState = 'welcome';
public ?string $selectedServerType = null;
public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null;
public ?string $privateKey = null;
@@ -25,6 +29,7 @@ class Index extends Component
public ?PrivateKey $createdPrivateKey = null;
public ?Collection $servers = null;
public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null;
@@ -36,7 +41,8 @@ class Index extends Component
public ?Server $createdServer = null;
public Collection $projects;
public ?int $selectedExistingProject = null;
public ?int $selectedProject = null;
public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false;
@@ -60,6 +66,26 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host';
}
// if ($this->currentState === 'create-project') {
// $this->getProjects();
// }
// if ($this->currentState === 'create-resource') {
// $this->selectExistingServer();
// $this->selectExistingProject();
// }
// if ($this->currentState === 'private-key') {
// $this->setServerType('remote');
// }
// if ($this->currentState === 'create-server') {
// $this->selectExistingPrivateKey();
// }
// if ($this->currentState === 'validate-server') {
// $this->selectExistingServer();
// }
// if ($this->currentState === 'select-existing-server') {
// $this->selectExistingServer();
// }
}
public function explanation()
{
@@ -71,7 +97,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function restartBoarding()
{
return redirect()->route('boarding');
return redirect()->route('onboarding');
}
public function skipBoarding()
{
@@ -87,13 +113,18 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0);
$this->selectedExistingServer = 0;
if (!$this->createdServer) {
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
}
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
return $this->validateServer('localhost');
} elseif ($this->selectedServerType === 'remote') {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if (isDev()) {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get();
} else {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
}
if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
}
@@ -116,19 +147,25 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->validateServer();
$this->currentState = 'validate-server';
}
public function getProxyType()
{
$proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) {
$this->currentState = 'select-proxy';
return;
}
// Set Default Proxy Type
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {
// $this->currentState = 'select-proxy';
// return;
// }
$this->getProjects();
}
public function selectExistingPrivateKey()
{
if (is_null($this->selectedExistingPrivateKey)) {
$this->restartBoarding();
return;
}
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server';
@@ -188,14 +225,20 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->validateServer();
$this->selectedExistingServer = $this->createdServer->id;
$this->currentState = 'validate-server';
}
public function installServer()
{
$this->dispatch('init', true);
}
public function validateServer()
{
try {
config()->set('coolify.mux_enabled', false);
instant_remote_process(['uptime'], $this->createdServer, true);
// EC2 does not have `uptime` command, lol
instant_remote_process(['ls /'], $this->createdServer, true);
$this->createdServer->settings()->update([
'is_reachable' => true,
@@ -210,7 +253,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->currentState = 'install-docker';
$this->currentState = 'validate-server';
throw new \Exception('Docker not found or old version is installed.');
}
$this->createdServer->settings()->update([
@@ -218,27 +261,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]);
$this->getProxyType();
} catch (\Throwable $e) {
// $this->dockerInstallationStarted = false;
return handleError(error: $e, livewire: $this);
}
}
public function installDocker()
{
try {
$this->dockerInstallationStarted = true;
$activity = InstallDocker::run($this->createdServer);
$this->dispatch('installDocker');
$this->dispatch('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
$this->dockerInstallationStarted = false;
return handleError(error: $e, livewire: $this);
}
}
public function dockerInstalledOrSkipped()
{
$this->validateServer();
}
public function selectProxy(string|null $proxyType = null)
public function selectProxy(?string $proxyType = null)
{
if (!$proxyType) {
return $this->getProjects();
@@ -253,13 +279,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id;
$this->selectedProject = $this->projects->first()->id;
}
$this->currentState = 'create-project';
}
public function selectExistingProject()
{
$this->createdProject = Project::find($this->selectedExistingProject);
$this->createdProject = Project::find($this->selectedProject);
$this->currentState = 'create-resource';
}
public function createNewProject()

View File

@@ -2,18 +2,46 @@
namespace App\Livewire;
use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Artisan;
use Livewire\Component;
class Dashboard extends Component
{
public $projects = [];
public $servers = [];
public Collection $servers;
public Collection $private_keys;
public $deployments_per_server;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->get_deployments();
}
public function cleanup_queue()
{
$this->dispatch('success', 'Cleanup started.');
Artisan::queue('cleanup:application-deployment-queue', [
'--team-id' => currentTeam()->id
]);
}
public function get_deployments()
{
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $this->servers->pluck("id"))->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
}
// public function getIptables()
// {

View File

@@ -5,7 +5,7 @@ namespace App\Livewire\Destination\New;
use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -14,7 +14,7 @@ class Docker extends Component
public string $name;
public string $network;
public Collection $servers;
public ?Collection $servers = null;
public Server $server;
public ?int $server_id = null;
public bool $is_swarm = false;
@@ -34,6 +34,9 @@ class Docker extends Component
public function mount()
{
if (is_null($this->servers)) {
$this->servers = Server::isReachable()->get();
}
if (request()->query('server_id')) {
$this->server_id = request()->query('server_id');
} else {
@@ -46,7 +49,9 @@ class Docker extends Component
} else {
$this->network = new Cuid2(7);
}
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
if ($this->servers->count() > 0) {
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
}
}
public function generate_name()

View File

@@ -3,6 +3,8 @@
namespace App\Livewire\Destination;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Livewire\Component;
@@ -11,6 +13,40 @@ class Show extends Component
public Server $server;
public Collection|array $networks = [];
private function createNetworkAndAttachToProxy()
{
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
public function add($name)
{
if ($this->server->isSwarm()) {
$found = $this->server->swarmDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
SwarmDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $this->name,
'server_id' => $this->server->id,
]);
}
} else {
$found = $this->server->standaloneDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
StandaloneDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $name,
'server_id' => $this->server->id,
]);
}
$this->createNetworkAndAttachToProxy();
}
}
public function scan()
{
if ($this->server->isSwarm()) {
@@ -26,6 +62,8 @@ class Show extends Component
});
if ($this->networks->count() === 0) {
$this->dispatch('success', 'No new networks found.');
return;
}
$this->dispatch('success', 'Scan done.');
}
}

View File

@@ -2,8 +2,10 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
@@ -28,9 +30,8 @@ class Help extends Component
public function submit()
{
try {
$this->rateLimit(3, 60);
$this->rateLimit(3, 30);
$this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}";
$mail = new MailMessage();
$mail->view(
@@ -40,9 +41,21 @@ class Help extends Component
'debug' => $debug
]
);
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->dispatch('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
$mail->subject("[HELP]: {$this->subject}");
$settings = InstanceSettings::get();
$type = set_transanctional_email_settings($settings);
if (!$type) {
$url = "https://app.coolify.io/api/feedback";
if (isDev()) {
$url = "http://localhost:80/api/feedback";
}
Http::post($url, [
'content' => "User: `" . auth()->user()?->email . "` with subject: `" . $this->subject . "` has the following problem: `" . $this->description . "`"
]);
} else {
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
}
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -4,7 +4,7 @@ namespace App\Livewire;
use Livewire\Component;
class Sponsorship extends Component
class LayoutPopups extends Component
{
public function getListeners()
{
@@ -17,12 +17,8 @@ class Sponsorship extends Component
{
$this->dispatch('success', 'Realtime events configured!');
}
public function disable()
{
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
}
public function render()
{
return view('livewire.sponsorship');
return view('livewire.layout-popups');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Livewire;
use App\Models\User;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
class NewActivityMonitor extends Component
{
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->hydrateActivity();
$this->isPollingActive = true;
}
public function hydrateActivity()
{
$this->activity = Activity::find($this->activityId);
}
public function polling()
{
$this->hydrateActivity();
// $this->setStatus(ProcessStatus::IN_PROGRESS);
$exit_code = data_get($this->activity, 'properties.exitCode');
if ($exit_code !== null) {
// if ($exit_code === 0) {
// // $this->setStatus(ProcessStatus::FINISHED);
// } else {
// // $this->setStatus(ProcessStatus::ERROR);
// }
$this->isPollingActive = false;
if ($this->eventToDispatch !== null) {
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
$causer_id = data_get($this->activity, 'causer_id');
$user = User::find($causer_id);
if ($user) {
foreach ($user->teams as $team) {
$teamId = $team->id;
$this->eventToDispatch::dispatch($teamId);
}
}
return;
}
$this->dispatch($this->eventToDispatch);
ray('Dispatched event: ' . $this->eventToDispatch);
}
}
}
}

View File

@@ -6,7 +6,7 @@ use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class DiscordSettings extends Component
class Discord extends Component
{
public Team $team;
protected $rules = [
@@ -55,4 +55,8 @@ class DiscordSettings extends Component
$this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.');
}
public function render()
{
return view('livewire.notifications.discord');
}
}

View File

@@ -2,13 +2,12 @@
namespace App\Livewire\Notifications;
use Livewire\Component;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
use Log;
class EmailSettings extends Component
class Email extends Component
{
public Team $team;
public string $emails;
@@ -63,7 +62,7 @@ class EmailSettings extends Component
]);
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved successfully.');
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -71,7 +70,7 @@ class EmailSettings extends Component
public function sendTestNotification()
{
$this->team?->notify(new Test($this->emails));
$this->dispatch('success', 'Test Email sent successfully.');
$this->dispatch('success', 'Test Email sent.');
}
public function instantSaveInstance()
{
@@ -83,7 +82,7 @@ class EmailSettings extends Component
$this->team->resend_enabled = false;
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved successfully.');
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -119,19 +118,21 @@ class EmailSettings extends Component
{
try {
$this->resetErrorBag();
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.smtp_host' => 'required',
'team.smtp_port' => 'required|numeric',
'team.smtp_encryption' => 'nullable',
'team.smtp_username' => 'nullable',
'team.smtp_password' => 'nullable',
'team.smtp_timeout' => 'nullable',
]);
if (!$this->team->use_instance_email_settings) {
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.smtp_host' => 'required',
'team.smtp_port' => 'required|numeric',
'team.smtp_encryption' => 'nullable',
'team.smtp_username' => 'nullable',
'team.smtp_password' => 'nullable',
'team.smtp_timeout' => 'nullable',
]);
}
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved successfully.');
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return handleError($e, $this);
@@ -148,7 +149,7 @@ class EmailSettings extends Component
]);
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved successfully.');
$this->dispatch('success', 'Settings saved.');
} catch (\Throwable $e) {
$this->team->resend_enabled = false;
return handleError($e, $this);
@@ -189,4 +190,8 @@ class EmailSettings extends Component
}
$this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
}
public function render()
{
return view('livewire.notifications.email');
}
}

View File

@@ -6,8 +6,9 @@ use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class TelegramSettings extends Component
class Telegram extends Component
{
public Team $team;
protected $rules = [
'team.telegram_enabled' => 'nullable|boolean',
@@ -61,4 +62,8 @@ class TelegramSettings extends Component
$this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.');
}
public function render()
{
return view('livewire.notifications.telegram');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Profile;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -10,6 +11,10 @@ class Index extends Component
public int $userId;
public string $email;
public string $current_password;
public string $new_password;
public string $new_password_confirmation;
#[Validate('required')]
public string $name;
public function mount()
@@ -19,15 +24,43 @@ class Index extends Component
$this->email = auth()->user()->email;
}
public function submit()
{
try {
$this->validate();
$this->validate([
'name' => 'required',
]);
auth()->user()->update([
'name' => $this->name,
]);
$this->dispatch('success', 'Profile updated successfully.');
$this->dispatch('success', 'Profile updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function resetPassword()
{
try {
$this->validate([
'current_password' => 'required',
'new_password' => 'required|min:8',
'new_password_confirmation' => 'required|min:8|same:new_password',
]);
if (!Hash::check($this->current_password, auth()->user()->password)) {
$this->dispatch('error', 'Current password is incorrect.');
return;
}
if ($this->new_password !== $this->new_password_confirmation) {
$this->dispatch('error', 'The two new passwords does not match.');
return;
}
auth()->user()->update([
'password' => Hash::make($this->new_password),
]);
$this->dispatch('success', 'Password updated.');
$this->current_password = '';
$this->new_password = '';
$this->new_password_confirmation = '';
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -8,19 +8,31 @@ use Livewire\Component;
class Advanced extends Component
{
public Application $application;
public bool $is_force_https_enabled;
public bool $is_gzip_enabled;
public bool $is_stripprefix_enabled;
protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required',
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
'application.settings.is_force_https_enabled' => 'boolean|required',
'is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.is_gzip_enabled' => 'boolean|required',
'application.settings.is_stripprefix_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
];
public function mount() {
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
$this->is_gzip_enabled = $this->application->isGzipEnabled();
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
}
public function instantSave()
{
if ($this->application->isLogDrainEnabled()) {
@@ -30,7 +42,16 @@ class Advanced extends Component
return;
}
}
if ($this->application->settings->is_force_https_enabled) {
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_gzip_enabled !== $this->is_gzip_enabled) {
$this->application->settings->is_gzip_enabled = $this->is_gzip_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($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->application->settings->save();

View File

@@ -10,7 +10,7 @@ class Configuration extends Component
{
public Application $application;
public $servers;
protected $listeners = ['build_pack_updated' => '$refresh'];
protected $listeners = ['buildPackUpdated' => '$refresh'];
public function mount()
{

View File

@@ -9,12 +9,13 @@ use Livewire\Component;
class Index extends Component
{
public Application $application;
public array|Collection $deployments = [];
public ?Collection $deployments;
public int $deployments_count = 0;
public string $current_url;
public int $skip = 0;
public int $default_take = 40;
public bool $show_next = false;
public bool $show_prev = false;
public ?string $pull_request_id = null;
protected $queryString = ['pull_request_id'];
public function mount()
@@ -47,9 +48,9 @@ class Index extends Component
}
private function show_more()
{
if (count($this->deployments) !== 0) {
if ($this->deployments->count() !== 0) {
$this->show_next = true;
if (count($this->deployments) < $this->default_take) {
if ($this->deployments->count() < $this->default_take) {
$this->show_next = false;
}
return;
@@ -60,15 +61,29 @@ class Index extends Component
{
$this->load_deployments();
}
public function load_deployments(int|null $take = null)
public function previous_page(?int $take = null)
{
if ($take) {
$this->skip = $this->skip - $take;
}
$this->skip = $this->skip - $this->default_take;
if ($this->skip < 0) {
$this->show_prev = false;
$this->skip = 0;
}
$this->load_deployments();
}
public function next_page(?int $take = null)
{
if ($take) {
$this->skip = $this->skip + $take;
}
$take = $this->default_take;
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
$this->show_prev = true;
$this->load_deployments();
}
public function load_deployments()
{
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take);
$this->deployments = $deployments;
$this->deployments_count = $count;
$this->show_pull_request_only();

View File

@@ -7,8 +7,6 @@ use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
use Livewire\Component;
class DeploymentNavbar extends Component
@@ -37,17 +35,24 @@ class DeploymentNavbar extends Component
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->dispatch('refreshQueue');
}
public function force_start()
{
try {
force_start_deployment($this->application_deployment_queue);
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
}
}
public function cancel()
{
try {
$kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}";
if ($this->application_deployment_queue->current_process_id) {
$process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers");
if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) {
Process::run($kill_command);
}
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
$server = Server::find($server_id);
if ($this->application_deployment_queue->logs) {
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
$new_log_entry = [
'command' => $kill_command,
'output' => "Deployment cancelled by user.",
@@ -61,14 +66,15 @@ class DeploymentNavbar extends Component
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
]);
}
instant_remote_process([$kill_command], $server);
} catch (\Throwable $e) {
ray($e);
return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
queue_next_deployment($this->application);
}
}
}

View File

@@ -61,12 +61,18 @@ class General extends Component
'application.docker_compose_pr' => 'nullable',
'application.docker_compose_raw' => 'nullable',
'application.docker_compose_pr_raw' => 'nullable',
'application.custom_labels' => 'nullable',
'application.dockerfile_target_build' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.docker_compose_custom_start_command' => 'nullable',
'application.docker_compose_custom_build_command' => 'nullable',
'application.custom_labels' => 'nullable',
'application.custom_docker_run_options' => 'nullable',
'application.pre_deployment_command' => 'nullable',
'application.pre_deployment_command_container' => 'nullable',
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -96,10 +102,12 @@ class General extends Component
'application.docker_compose_pr_raw' => 'Docker compose raw',
'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build',
'application.settings.is_static' => 'Is static',
'application.custom_docker_run_options' => 'Custom docker run commands',
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'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',
];
public function mount()
{
@@ -108,6 +116,10 @@ class General extends Component
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->fqdn = null;
$this->application->settings->save();
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
@@ -116,13 +128,12 @@ class General extends Component
}
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
}
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->checkLabelUpdates();
}
public function instantSave()
{
@@ -160,6 +171,12 @@ class General extends Component
}
return $domain;
}
public function updatedApplicationBaseDirectory() {
raY('asdf');
if ($this->application->build_pack === 'dockercompose') {
$this->loadComposeFile();
}
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
@@ -178,17 +195,8 @@ class General extends Component
$this->resetDefaultLabels(false);
}
$this->submit();
$this->dispatch('build_pack_updated');
$this->dispatch('buildPackUpdated');
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
}
}
public function getWildcardDomain()
{
$server = data_get($this->application, 'destination.server');
@@ -208,13 +216,19 @@ class General extends Component
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->application->save();
$this->resetDefaultLabels(false);
// $this->dispatch('success', 'Labels reset to default!');
}
public function submit($showToaster = true)
{
try {
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
@@ -227,27 +241,26 @@ class General extends Component
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'application.docker_registry_image_tag' => 'required',
]);
}
if (data_get($this->application, 'fqdn')) {
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$domains = $domains->unique();
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='dark:text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
}
}
}
check_fqdn_usage($this->application);
$this->application->fqdn = $domains->implode(',');
}
if (data_get($this->application, 'custom_docker_run_options')) {
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
}
if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port && !$this->application->ports_exposes) {
@@ -262,7 +275,11 @@ class General extends Component
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->parsedServices = $this->application->parseCompose();
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->save();
@@ -270,7 +287,6 @@ class General extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Events\ApplicationStatusChanged;
use App\Jobs\ComplexContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;
@@ -32,14 +33,11 @@ class Heading extends Component
{
if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}
if ($showNotification) $this->dispatch('success', "Application status updated.");
if ($showNotification) $this->dispatch('success', "Success", "Application status updated.");
}
public function force_deploy_without_cache()
@@ -47,39 +45,27 @@ class Heading extends Component
$this->deploy(force_rebuild: true);
}
public function deployNew()
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->dispatch('error', 'Please load a Compose file first.');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
force_rebuild: false,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
public function deploy(bool $force_rebuild = false)
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->dispatch('error', 'Please load a Compose file first.');
$this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.');
return;
}
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'Please set a Docker image name first.');
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
return;
}
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
@@ -102,29 +88,23 @@ class Heading extends Component
StopApplication::run($this->application);
$this->application->status = 'exited';
$this->application->save();
$this->application->refresh();
}
public function restartNew()
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
is_new_deployment: true,
);
return redirect()->route('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
if ($this->application->additional_servers->count() > 0) {
$this->application->additional_servers->each(function ($server) {
$server->pivot->status = "exited:unhealthy";
$server->pivot->save();
});
}
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
public function restart()
{
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
application: $this->application,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);

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