Compare commits

...

383 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
Matt
e6ca8cd167 fix: only add 'networks' key if 'network_mode' is absent 2024-02-11 09:22:09 -06: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
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
573 changed files with 17435 additions and 7397 deletions

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

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 {
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,6 @@ 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";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -122,7 +122,6 @@ 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";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -106,7 +106,6 @@ 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";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -128,7 +128,6 @@ 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";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -117,7 +117,6 @@ 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";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

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.");

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,7 +2,7 @@
namespace App\Actions\Proxy;
use App\Events\ProxyStatusChanged;
use App\Events\ProxyStarted;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -15,8 +15,11 @@ class StartProxy
{
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");
@@ -34,8 +37,10 @@ class StartProxy
"echo 'Proxy started successfully.'"
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([
"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

@@ -45,6 +45,9 @@ class DeleteService
foreach ($service->databases()->get() as $database) {
$database->forceDelete();
}
foreach ($service->scheduled_tasks as $task) {
$task->delete();
}
$service->tags()->detach();
}
}

View File

@@ -13,14 +13,10 @@ class StartService
{
ray('Starting service: ' . $service->name);
$service->saveComposeConfigs();
$service_name = addslashes($service->name);
$server_name = addslashes($service->server->name);
$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[] = "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";

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

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ScheduledTask;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -108,6 +109,17 @@ class CleanupStuckedResources extends Command
} 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 {

View File

@@ -8,15 +8,16 @@ use Illuminate\Console\Command;
class CleanupUnreachableServers extends Command
{
protected $signature = 'cleanup:unreachable-servers';
protected $description = 'Cleanup Unreachable Servers (3 days)';
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(3))->get();
$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,10 +2,12 @@
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;
@@ -15,6 +17,7 @@ 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 Exception;
use Illuminate\Console\Command;
@@ -54,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',
@@ -67,8 +72,12 @@ class Emails extends Command
],
);
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (isDev()) {
$this->email = "test@example.com";
} else {
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to');
$this->email = text('Email Address to send to:');
}
}
set_transanctional_email_settings();
@@ -111,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

@@ -35,7 +35,8 @@ class Init extends Command
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
try {
setup_dynamic_configuration();
$server = Server::find(0)->first();
$server->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
@@ -125,6 +126,9 @@ class Init extends Command
// Cleanup any failed deployments
try {
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);

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

@@ -4,7 +4,6 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\ComplexContainerStatusJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
@@ -48,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)
@@ -77,6 +80,35 @@ class Kernel extends ConsoleKernel
foreach ($servers as $server) {
$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)
{
@@ -120,8 +152,8 @@ class Kernel extends ConsoleKernel
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');

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

@@ -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

@@ -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

@@ -9,17 +9,36 @@ 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 Illuminate\Support\Collection;
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)
{
$token = auth()->user()->currentAccessToken();
$teamId = data_get($token, 'team_id');
$teamId = get_team_id_from_token();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
@@ -28,7 +47,7 @@ class Deploy extends Controller
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 response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
return invalid_token();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
@@ -45,16 +64,22 @@ class Deploy extends Controller
if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$message = collect([]);
$deployments = collect();
$payload = collect();
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
['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 ($message->count() > 0) {
return response()->json(['message' => $message->toArray()], 200);
}
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);
}
@@ -67,10 +92,12 @@ class Deploy extends Controller
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.");
// $message->push("Tag {$tag} not found.");
continue;
}
$applications = $found_tag->applications()->get();
@@ -80,83 +107,78 @@ class Deploy extends Controller
continue;
}
foreach ($applications as $resource) {
$return_message = $this->deploy_resource($resource, $force);
['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) {
$return_message = $this->deploy_resource($resource, $force);
['message' => $return_message] = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
ray($message);
if ($message->count() > 0) {
return response()->json(['message' => $message->toArray()], 200);
$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.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
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): Collection
public function deploy_resource($resource, bool $force = false): array
{
$message = collect([]);
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
return $message->push("Resource ($resource) not found.");
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: new Cuid2(7),
deployment_uuid: $deployment_uuid,
force_rebuild: $force,
);
$message->push("Application {$resource->name} deployment queued.");
$message = "Application {$resource->name} deployment queued.";
} else if ($type === 'App\Models\StandalonePostgresql') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartPostgresql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message->push("Database {$resource->name} started.");
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneRedis') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartRedis::run($resource);
$resource->update([
'started_at' => now(),
]);
$message->push("Database {$resource->name} started.");
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMongodb') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMongodb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message->push("Database {$resource->name} started.");
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMysql') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMysql::run($resource);
$resource->update([
'started_at' => now(),
]);
$message->push("Database {$resource->name} started.");
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\StandaloneMariadb') {
if (str($resource->status)->startsWith('running')) {
$message->push("Database {$resource->name} already running.");
}
StartMariadb::run($resource);
$resource->update([
'started_at' => now(),
]);
$message->push("Database {$resource->name} started.");
$message = "Database {$resource->name} started.";
} else if ($type === 'App\Models\Service') {
StartService::run($resource);
$message->push("Service {$resource->name} started. It could take a while, be patient.");
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
return $message;
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'
];
}

View File

@@ -24,6 +24,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\Url\Url;
@@ -66,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private Server $mainServer;
private ?ApplicationPreview $preview = null;
private ?string $git_type = null;
private bool $only_this_server = false;
private string $container_name;
private ?string $currently_running_container_name = null;
@@ -91,6 +93,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private ?string $buildTarget = null;
private Collection $saved_outputs;
private ?string $full_healthcheck_url = null;
private bool $custom_healthcheck_found = false;
private string $serverUser = 'root';
private string $serverUserHomeDir = '/root';
@@ -115,6 +118,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->commit = $this->application_deployment_queue->commit;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server;
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
@@ -133,6 +137,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
ray('New container name: ', $this->container_name);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -163,13 +169,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->is_github_based()) {
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
}
if ($this->application->build_pack === 'dockerfile') {
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
}
}
}
public function handle(): void
{
try {
// Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
if (!is_null($allContainers)) {
$allContainers = format_docker_command_output_to_json($allContainers);
$ips = collect([]);
@@ -207,12 +220,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$teamId = data_get($this->application, 'environment.project.team.id');
$buildServers = Server::buildServers($teamId)->get();
if ($buildServers->count() === 0) {
$this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server.");
$this->application_deployment_queue->addLogEntry("No suitable build server found. Using the deployment server.");
$this->build_server = $this->server;
$this->original_server = $this->server;
} else {
$this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed.");
$this->build_server = $buildServers->random();
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
$this->original_server = $this->server;
$this->use_build_server = true;
}
@@ -221,7 +234,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->build_server = $this->server;
$this->original_server = $this->server;
}
try {
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
$this->just_restart();
if ($this->server->isProxyShouldRun()) {
@@ -229,6 +241,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(false);
$this->run_post_deployment_command();
return;
} else if ($this->pull_request_id !== 0) {
$this->deploy_pull_request();
@@ -263,6 +276,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
}
}
$this->run_post_deployment_command();
$this->application->isConfigurationChanged(true);
} catch (Exception $e) {
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
@@ -284,13 +298,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true,
]
);
$this->execute_remote_command(
[
"docker image prune -f >/dev/null 2>&1",
"hidden" => true,
"ignore_errors" => true,
]
);
// $this->execute_remote_command(
// [
// "docker image prune -f >/dev/null 2>&1",
// "hidden" => true,
// "ignore_errors" => true,
// ]
// );
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
@@ -329,7 +347,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function deploy_dockerimage_buildpack()
{
$this->dockerImage = $this->application->docker_registry_image_name;
if (str($this->application->docker_registry_image_tag)->isEmpty()) {
$this->dockerImageTag = 'latest';
} else {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'");
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
$this->generate_image_names();
@@ -360,6 +382,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->cleanup_git();
$this->application->loadComposeFile(isInit: false);
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
$yaml = $composeFile = $this->application->docker_compose_raw;
} else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
@@ -398,32 +421,52 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
}
$this->write_deployment_configurations();
// Start compose file
if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
$this->write_deployment_configurations();
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
);
}
} else {
if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
$this->write_deployment_configurations();
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
$this->write_deployment_configurations();
}
}
$this->application_deployment_queue->addLogEntry("New container started.");
}
private function deploy_dockerfile_buildpack()
{
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
if ($this->use_build_server) {
$this->server = $this->build_server;
}
if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location;
}
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->clone_repository();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
@@ -435,7 +478,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return;
}
}
$this->clone_repository();
$this->cleanup_git();
$this->generate_compose_file();
$this->generate_build_env_variables();
@@ -514,9 +556,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
if ($this->pull_request_id === 0) {
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
} else {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
@@ -711,9 +755,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
}
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
}
if ($this->pull_request_id !== 0) {
$this->application->settings->is_consistent_container_name_enabled = true;
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
}
if (str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry("Custom IP address is set, rolling update is not supported.");
}
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
@@ -732,7 +788,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->server->isSwarm()) {
// Implement healthcheck for swarm
} else {
if ($this->application->isHealthcheckDisabled()) {
if ($this->application->isHealthcheckDisabled() && $this->custom_healthcheck_found === false) {
$this->newVersionIsHealthy = true;
return;
}
@@ -765,13 +821,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
break;
}
$counter++;
sleep($this->application->health_check_interval);
Sleep::for($this->application->health_check_interval)->seconds();
}
}
}
}
private function deploy_pull_request()
{
if ($this->application->build_pack === 'dockercompose') {
$this->deploy_docker_compose_buildpack();
return;
}
if ($this->use_build_server) {
$this->server = $this->build_server;
}
@@ -791,26 +851,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->add_build_env_variables_to_dockerfile();
}
$this->build_image();
$this->stop_running_container();
if ($this->application->destination->server->isSwarm()) {
$this->push_to_docker_registry();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
],
);
} else {
$this->application_deployment_queue->addLogEntry("Starting preview deployment.");
if ($this->use_build_server) {
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
}
// $this->stop_running_container();
$this->rolling_update();
}
private function create_workdir()
{
@@ -847,8 +890,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
],
);
$this->run_pre_deployment_command();
}
private function deploy_to_additional_destinations()
{
@@ -880,7 +923,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
destination: $destination,
no_questions_asked: true,
);
$this->application_deployment_queue->addLogEntry("Deploying to additional server: {$server->name}. Click here to see the deployment status: " . route('project.application.deployment.show', [
$this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
'application_uuid' => data_get($this->application, 'uuid'),
'deployment_uuid' => $deployment_uuid,
@@ -1015,13 +1058,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->env_nixpacks_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->nixpacks_environment_variables as $env) {
if (!is_null($env->real_value)) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
}
}
} else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if (!is_null($env->real_value)) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
}
}
}
$this->env_nixpacks_args = $this->env_nixpacks_args->implode(' ');
}
@@ -1030,20 +1077,27 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->env_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
if (!is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
}
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
if (!is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
}
}
}
$this->env_args->put('SOURCE_COMMIT', $this->commit);
}
private function generate_compose_file()
{
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
$onlyPort = null;
if (count($ports) > 0) {
$onlyPort = $ports[0];
}
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
@@ -1054,6 +1108,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$labels = $labels->filter(function ($value, $key) {
return !Str::startsWith($value, 'coolify.');
});
$found_caddy_labels = $labels->filter(function ($value, $key) {
return Str::startsWith($value, 'caddy_');
});
if ($found_caddy_labels->count() === 0) {
if ($this->pull_request_id !== 0) {
$domains = str(data_get($this->preview, 'fqdn'))->explode(',');
} else {
$domains = str(data_get($this->application, 'fqdn'))->explode(',');
}
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $this->application->destination->network,
uuid: $this->application->uuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $this->application->isForceHttpsEnabled(),
is_gzip_enabled: $this->application->isGzipEnabled(),
is_stripprefix_enabled: $this->application->isStripprefixEnabled()
));
}
$this->application->custom_labels = base64_encode($labels->implode("\n"));
$this->application->save();
} else {
@@ -1063,6 +1136,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
// Check for custom HEALTHCHECK
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile_from_repo', "ignore_errors" => true
]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
if (str($dockerfile)->contains('HEALTHCHECK')) {
$this->custom_healthcheck_found = true;
}
}
$docker_compose = [
'version' => '3.8',
'services' => [
@@ -1075,16 +1160,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'networks' => [
$this->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
$this->generate_healthcheck_commands()
],
'interval' => $this->application->health_check_interval . 's',
'timeout' => $this->application->health_check_timeout . 's',
'retries' => $this->application->health_check_retries,
'start_period' => $this->application->health_check_start_period . 's'
],
'mem_limit' => $this->application->limits_memory,
'memswap_limit' => $this->application->limits_memory_swap,
'mem_swappiness' => $this->application->limits_memory_swappiness,
@@ -1101,6 +1176,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [
'test' => [
'CMD-SHELL',
$this->generate_healthcheck_commands()
],
'interval' => $this->application->health_check_interval . 's',
'timeout' => $this->application->health_check_timeout . 's',
'retries' => $this->application->health_check_retries,
'start_period' => $this->application->health_check_start_period . 's'
];
}
if (!is_null($this->application->limits_cpuset)) {
data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset);
}
@@ -1199,14 +1286,45 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// ];
// }
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
if ($this->pull_request_id === 0) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
} else {
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
data_forget($custom_compose, 'ip');
data_forget($custom_compose, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$this->container_name], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
}
}
}
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
@@ -1252,23 +1370,53 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables = collect();
if ($this->pull_request_id === 0) {
foreach ($this->application->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
// This is necessary because we have to escape the value of the environment variable
// but only if the environment variable is created after 4.0.0-beta.240
// when I implemented the escaping feature.
// Old environment variables are not escaped, because it could break the application
// as the application could expect the unescaped value.
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$environment_variables->push("HOST=0.0.0.0");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
@@ -1481,22 +1629,27 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($this->pull_request_id === 0) {
$containers = $containers->filter(function ($container) {
return data_get($container, 'Names') !== $this->container_name;
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
});
}
$containers->each(function ($container) {
$containerName = data_get($container, 'Names');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
$this->execute_remote_command(
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
}
} else {
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::FAILED->value,
]);
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
);
}
}
@@ -1580,27 +1733,84 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
]);
}
private function run_pre_deployment_command()
{
if (empty($this->application->pre_deployment_command)) {
return;
}
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($containers->count() == 0) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}");
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"';
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
],
);
return;
}
}
throw new RuntimeException('Pre-deployment command: Could not find a valid container. Is the container name correct?');
}
private function run_post_deployment_command()
{
if (empty($this->application->post_deployment_command)) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}");
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"';
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
],
);
return;
}
}
throw new RuntimeException('Post-deployment command: Could not find a valid container. Is the container name correct?');
}
private function next(string $status)
{
queue_next_deployment($this->application);
// If the deployment is cancelled by the user, don't update the status
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
if (
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
) {
$this->application_deployment_queue->update([
'status' => $status,
]);
}
if ($status === ApplicationDeploymentStatus::FAILED->value) {
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) {
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
return;
}
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
if (!$this->only_this_server) {
$this->deploy_to_additional_destinations();
}
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
}
}
public function failed(Throwable $exception): void
{
$this->next(ApplicationDeploymentStatus::FAILED->value);
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
if (str($exception->getMessage())->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
@@ -1608,15 +1818,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->application->build_pack !== 'dockercompose') {
$code = $exception->getCode();
ray($code);
if ($code !== 69420) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true]
);
}
}
$this->next(ApplicationDeploymentStatus::FAILED->value);
}
}

View File

@@ -30,6 +30,9 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
try {
if ($this->application->is_public_repository()) {
return;
}
if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment();
return;

View File

@@ -8,6 +8,7 @@ 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;
@@ -43,22 +44,24 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
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) {
$is_main_server = $application->destination->server->id === $this->server->id;
if ($is_main_server) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$applications = $applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
}
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$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);
@@ -147,15 +150,16 @@ 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 ($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()) {
@@ -165,6 +169,35 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
}
}
} else {
$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));
}
@@ -173,6 +206,8 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}

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

@@ -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

@@ -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
{
@@ -41,15 +41,6 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
throw new \RuntimeException('Server is not ready.');
};
try {
// $this->server->validateConnection();
// $this->server->validateOS();
// $docker_installed = $this->server->validateDockerEngine();
// if (!$docker_installed) {
// $this->server->installDocker();
// $this->server->validateDockerEngine();
// }
// $this->server->validateDockerEngineVersion();
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

@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
public bool $showWaiting = false;
protected $activity;
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];

View File

@@ -3,7 +3,7 @@
namespace App\Livewire\Admin;
use App\Models\User;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class Index extends Component
@@ -14,28 +14,26 @@ class Index extends Component
if (!isCloud()) {
return redirect()->route('dashboard');
}
if (auth()->user()->id !== 0 && session('adminToken') === null) {
if (auth()->user()->id !== 0) {
return redirect()->route('dashboard');
}
$this->users = User::whereHas('teams', function ($query) {
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
})->get();
})->get()->filter(function ($user) {
return $user->id !== 0;
});
}
public function switchUser(int $user_id)
{
$user = User::find($user_id);
auth()->login($user);
if ($user_id === 0) {
session()->forget('adminToken');
} else {
$token_payload = [
'valid' => true,
];
$token = Crypt::encrypt($token_payload);
session(['adminToken' => $token]);
if (auth()->user()->id !== 0) {
return redirect()->route('dashboard');
}
return refreshSession();
$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()
{

View File

@@ -2,22 +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;
@@ -27,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;
@@ -38,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;
@@ -62,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()
{
@@ -73,7 +97,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function restartBoarding()
{
return redirect()->route('boarding');
return redirect()->route('onboarding');
}
public function skipBoarding()
{
@@ -89,6 +113,7 @@ 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.');
}
@@ -122,10 +147,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->installServer();
$this->currentState = 'validate-server';
}
public function getProxyType()
{
// Set Default Proxy Type
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
// $proxyTypeSet = $this->createdServer->proxy->type;
// if (!$proxyTypeSet) {
@@ -136,6 +162,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
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';
@@ -195,18 +225,20 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->selectedExistingServer = $this->createdServer->id;
$this->currentState = 'validate-server';
}
public function installServer()
{
$this->dispatch('validateServer', true);
$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,
@@ -247,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

@@ -3,6 +3,7 @@
namespace App\Livewire;
use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Support\Collection;
@@ -13,9 +14,11 @@ class Dashboard extends Component
{
public $projects = [];
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();
@@ -23,8 +26,8 @@ class Dashboard extends Component
public function cleanup_queue()
{
$this->dispatch('success', 'Cleanup started.');
Artisan::queue('app:init', [
'--cleanup-deployments' => 'true'
Artisan::queue('cleanup:application-deployment-queue', [
'--team-id' => currentTeam()->id
]);
}
public function get_deployments()

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,8 +49,10 @@ class Docker extends Component
} else {
$this->network = new Cuid2(7);
}
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

@@ -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

@@ -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,6 +118,7 @@ class EmailSettings extends Component
{
try {
$this->resetErrorBag();
if (!$this->team->use_instance_email_settings) {
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
@@ -129,9 +129,10 @@ class EmailSettings extends Component
'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,20 +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()) {
@@ -31,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,7 +9,7 @@ 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;
@@ -48,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;
@@ -63,7 +63,6 @@ class Index extends Component
}
public function previous_page(?int $take = null)
{
if ($take) {
$this->skip = $this->skip - $take;
}

View File

@@ -66,6 +66,10 @@ class General extends Component
'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',
@@ -112,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;
@@ -120,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()
{
@@ -164,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') {
@@ -182,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');
@@ -212,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();
@@ -234,26 +244,20 @@ class General extends Component
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();
$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='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
$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();
}
@@ -271,15 +275,18 @@ class General extends Component
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
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();
$showToaster && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}

View File

@@ -33,15 +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()

View File

@@ -46,7 +46,7 @@ class Form extends Component
$this->validate();
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
$this->application->save();
$this->dispatch('success', 'Preview url template updated successfully.');
$this->dispatch('success', 'Preview url template updated.');
$this->generate_real_url();
}
}

View File

@@ -10,7 +10,8 @@ class Execution extends Component
public $backup;
public $executions;
public $s3s;
public function mount() {
public function mount()
{
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
@@ -34,6 +35,11 @@ class Execution extends Component
$this->executions = $executions;
$this->s3s = currentTeam()->s3s;
}
public function cleanupFailed()
{
$this->backup->executions()->where('status', 'failed')->delete();
$this->dispatch('refreshBackupExecutions');
}
public function render()
{
return view('livewire.project.database.backup.execution');

View File

@@ -42,7 +42,7 @@ class BackupEdit extends Component
public function delete()
{
// TODO: Delete backup from server and add a confirmation modal
try {
$this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous();
@@ -54,7 +54,9 @@ class BackupEdit extends Component
} else {
return redirect()->route('project.database.backup.index', $this->parameters);
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
@@ -63,7 +65,7 @@ class BackupEdit extends Component
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->dispatch('success', 'Backup updated successfully');
$this->dispatch('success', 'Backup updated successfully.');
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}

View File

@@ -33,8 +33,8 @@ class BackupExecutions extends Component
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
}
$execution->delete();
$this->dispatch('success', 'Backup deleted successfully.');
$this->dispatch('refreshBackupExecutions');
$this->dispatch('success', 'Backup deleted.');
$this->refreshBackupExecutions();
}
public function download($exeuctionId)
{
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
}
public function refreshBackupExecutions(): void
{
$this->executions = data_get($this->backup, 'executions', []);
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
}
}

View File

@@ -46,9 +46,9 @@ class General extends Component
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
$this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
}
}
public function instantSaveAdvanced() {
@@ -59,7 +59,7 @@ class General extends Component
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
@@ -73,7 +73,7 @@ class General extends Component
}
$this->validate();
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
@@ -93,7 +93,7 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);

View File

@@ -44,9 +44,9 @@ class General extends Component
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
$this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
}
}
public function instantSaveAdvanced()
@@ -58,7 +58,7 @@ class General extends Component
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
@@ -75,7 +75,7 @@ class General extends Component
}
$this->validate();
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
@@ -95,7 +95,7 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);

View File

@@ -46,9 +46,9 @@ class General extends Component
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
$this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
}
}
public function instantSaveAdvanced()
@@ -60,7 +60,7 @@ class General extends Component
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
@@ -74,7 +74,7 @@ class General extends Component
}
$this->validate();
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
@@ -94,7 +94,7 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);

View File

@@ -53,9 +53,9 @@ class General extends Component
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
$this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
}
}
public function instantSaveAdvanced() {
@@ -66,7 +66,7 @@ class General extends Component
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
@@ -87,7 +87,7 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
@@ -105,7 +105,7 @@ class General extends Component
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
$this->database->init_scripts = array_merge($this->database->init_scripts, [$script]);
$this->database->save();
$this->dispatch('success', 'Init script saved successfully.');
$this->dispatch('success', 'Init script saved.');
}
public function delete_init_script($script)
@@ -116,7 +116,7 @@ class General extends Component
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
$this->database->save();
$this->refresh();
$this->dispatch('success', 'Init script deleted successfully.');
$this->dispatch('success', 'Init script deleted.');
return;
}
}
@@ -148,7 +148,7 @@ class General extends Component
]
]);
$this->database->save();
$this->dispatch('success', 'Init script added successfully.');
$this->dispatch('success', 'Init script added.');
$this->new_content = '';
$this->new_filename = '';
}
@@ -161,7 +161,7 @@ class General extends Component
}
$this->validate();
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
}

View File

@@ -39,9 +39,9 @@ class General extends Component
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
$this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
}
}
public function instantSaveAdvanced() {
@@ -52,7 +52,7 @@ class General extends Component
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
@@ -66,7 +66,7 @@ class General extends Component
$this->database->redis_conf = null;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
}
@@ -86,7 +86,7 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);

View File

@@ -38,7 +38,7 @@ class ScheduledBackups extends Component
public function delete($scheduled_backup_id): void
{
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
$this->dispatch('success', 'Scheduled backup deleted successfully.');
$this->dispatch('success', 'Scheduled backup deleted.');
$this->refreshScheduledBackups();
}

View File

@@ -17,9 +17,14 @@ class Edit extends Component
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
@@ -41,10 +46,11 @@ class Edit extends Component
public function submit()
{
$this->validate();
try {
$this->validate();
$this->project->save();
$this->dispatch('saved');
$this->dispatch('success', 'Project updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -21,9 +21,14 @@ class EnvironmentEdit extends Component
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
@@ -10,7 +11,9 @@ class Index extends Component
{
public $projects;
public $servers;
public $private_keys;
public function mount() {
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->count();
}

View File

@@ -17,7 +17,6 @@ class DockerCompose extends Component
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
@@ -40,12 +39,17 @@ class DockerCompose extends Component
}
public function submit()
{
$server_id = $this->query['server_id'];
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$isValid = validateComposeFile($this->dockerComposeRaw, $server_id);
if ($isValid !== 'OK') {
return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
}
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
@@ -74,7 +78,6 @@ class DockerCompose extends Component
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -79,7 +79,10 @@ class GithubPrivateRepository extends Component
$this->loadRepositoryByPage();
}
}
$this->selected_repository_id = $this->repositories[0]['id'];
$this->repositories = $this->repositories->sortBy('name');
if ($this->repositories->count() > 0) {
$this->selected_repository_id = data_get($this->repositories->first(), 'id');
}
$this->current_step = 'repository';
}

View File

@@ -71,10 +71,10 @@ class Select extends Component
// }
// }
public function loadServices()
public function loadServices(bool $force = false)
{
try {
if (count($this->allServices) > 0) {
if (count($this->allServices) > 0 && !$force) {
if (!$this->search) {
$this->services = $this->allServices;
return;
@@ -90,8 +90,7 @@ class Select extends Component
$this->allServices = getServiceTemplates();
$this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search));
});;
$this->dispatch('success', 'Successfully loaded services.');
});
}
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -10,7 +10,8 @@ use Livewire\Component;
class Create extends Component
{
public $type;
public function mount() {
public function mount()
{
$services = getServiceTemplates();
$type = str(request()->query('type'));
$destination_uuid = request()->query('destination');
@@ -70,7 +71,7 @@ class Create extends Component
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$generatedValue = generateEnvValue($command->value());
$generatedValue = generateEnvValue($command->value(), $service);
}
EnvironmentVariable::create([
'key' => $key,

View File

@@ -104,7 +104,7 @@ class Index extends Component
'environment_name' => data_get($service, 'environment.name'),
'service_uuid' => data_get($service, 'uuid')
]);
$service->status = serviceStatus($service);
$service->status = $service->status();
}
return $service;
});

View File

@@ -17,9 +17,8 @@ class Configuration extends Component
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
"refreshStacks",
"checkStatus",
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
"check_status"
];
}
public function render()
@@ -37,21 +36,38 @@ class Configuration extends Component
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
public function restartApplication($id)
{
try {
$application = $this->service->applications->find($id);
if ($application) {
$application->restart();
$this->dispatch('success', 'Application restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function restartDatabase($id)
{
try {
$database = $this->service->databases->find($id);
if ($database) {
$database->restart();
$this->dispatch('success', 'Database restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function check_status()
{
try {
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
$this->dispatch('refresh')->self();
$this->dispatch('serviceStatusChanged');
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
$application->refresh();
});
$this->databases = $this->service->databases->sort();
$this->databases->each(function ($database) {
$database->refresh();
});
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -34,7 +34,11 @@ class Database extends Component
}
$this->refreshFileStorages();
}
public function instantSaveAdvanced()
public function instantSaveExclude()
{
$this->submit();
}
public function instantSaveLogDrain()
{
if (!$this->database->service->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
@@ -77,7 +81,7 @@ class Database extends Component
$this->validate();
$this->database->save();
updateCompose($this->database);
$this->dispatch('success', 'Database saved successfully.');
$this->dispatch('success', 'Database saved.');
} catch (\Throwable $e) {
ray($e);
} finally {

View File

@@ -1,11 +1,11 @@
<?php
namespace App\Livewire\Modal;
namespace App\Livewire\Project\Service;
use App\Models\Service;
use LivewireUI\Modal\ModalComponent;
use Livewire\Component;
class EditCompose extends ModalComponent
class EditCompose extends Component
{
public Service $service;
public $serviceId;
@@ -16,13 +16,13 @@ class EditCompose extends ModalComponent
public function mount() {
$this->service = Service::find($this->serviceId);
}
public function render()
{
return view('livewire.modal.edit-compose');
}
public function submit() {
public function saveEditedCompose() {
$this->dispatch('warning', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
$this->closeModal();
}
public function render()
{
return view('livewire.project.service.edit-compose');
}
}

View File

@@ -41,8 +41,8 @@ class FileStorage extends Component
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer($this->service);
$this->dispatch('success', 'File updated successfully.');
$this->fileStorage->saveStorageOnServer();
$this->dispatch('success', 'File updated.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);
$this->fileStorage->save();

View File

@@ -1,13 +0,0 @@
<?php
namespace App\Livewire\Project\Service;
use Livewire\Component;
class Modal extends Component
{
public function render()
{
return view('livewire.project.service.modal');
}
}

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