Compare commits

...

879 Commits

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

***Always include admin route, regardless of cloud status.
2024-02-08 14:01:16 +01:00
Andras Bacsai
9350fb4b97 Fix access control in Admin Index and hide Admin link in navbar 2024-02-08 13:54:16 +01:00
Andras Bacsai
59c3cc6ce1 Refactor admin authentication logic in Index component 2024-02-08 13:46:43 +01:00
Andras Bacsai
d7001937ac Fix access control in Admin Index and Navbar components 2024-02-08 13:40:26 +01:00
Andras Bacsai
3fe58ec66b Fix isInstanceAdmin function call in Index.php 2024-02-08 13:33:51 +01:00
Andras Bacsai
ed12f73483 Update admin authentication and version numbers 2024-02-08 13:33:34 +01:00
Andras Bacsai
6914280fb1 Merge pull request #1722 from coollabsio/next
v4.0.0-beta.212
2024-02-08 13:20:47 +01:00
Andras Bacsai
3dd5546369 Update destination.blade.php with server configurations 2024-02-08 13:19:11 +01:00
Andras Bacsai
576bff1af9 Remove exception and update server check in StopService 2024-02-08 13:17:08 +01:00
Andras Bacsai
3c4243d854 fix: go to prod env from dashboard if there is no other envs defined 2024-02-08 13:12:23 +01:00
Andras Bacsai
23d121d67a fix: make sure resources are deleted in async mode 2024-02-08 13:10:29 +01:00
Andras Bacsai
548304765c feat: cleanup queue 2024-02-08 12:47:00 +01:00
Andras Bacsai
037ba3ff79 Fix cleanup of halted deployments 2024-02-08 12:37:56 +01:00
Andras Bacsai
48b4c17391 Refactor init command to use full-cleanup option 2024-02-08 12:36:33 +01:00
Andras Bacsai
6acc0e6025 Add dynamic timeout for deployments 2024-02-08 12:34:01 +01:00
Andras Bacsai
43d7f746e4 Refactor destination.blade.php to include server and network information 2024-02-08 11:59:01 +01:00
Andras Bacsai
146fee14e5 Refactor destination.blade.php: Update server selection UI 2024-02-08 11:50:40 +01:00
Andras Bacsai
bde7fb2acb Fix user authentication condition in Index component 2024-02-08 11:46:23 +01:00
Andras Bacsai
08a729dc7b Add admin dashboard route and view 2024-02-08 11:45:19 +01:00
Andras Bacsai
7554de5993 Refactor app:init command and update cleanup options 2024-02-08 11:05:31 +01:00
Andras Bacsai
3d7295fec3 fix: new menu on navbar 2024-02-08 09:08:21 +01:00
Andras Bacsai
fd814abd8a Update version numbers to 4.0.0-beta.212 2024-02-07 20:44:17 +01:00
Andras Bacsai
4c38a59995 Merge branch 'main' into next 2024-02-07 20:35:35 +01:00
Andras Bacsai
642a6e3203 Merge pull request #1721 from coollabsio/quick-fix
Update database/service start commands
2024-02-07 20:34:58 +01:00
Andras Bacsai
9edbc15828 Update database start commands 2024-02-07 20:34:13 +01:00
Andras Bacsai
43eb2fb00b new navbar 2024-02-07 15:31:03 +01:00
Andras Bacsai
9a899deeb8 Fix DNS validation and error handling 2024-02-07 14:59:33 +01:00
Andras Bacsai
9e1a7d5d9a feat: multi deployments 2024-02-07 14:55:06 +01:00
Andras Bacsai
5bdbab7276 ui: specific about newrelic logdrains 2024-02-07 09:04:35 +01:00
Andras Bacsai
13bceb934f Refactor Application model and migration 2024-02-06 17:37:07 +01:00
Andras Bacsai
78b194cb16 Refactor application status update logic and add complex_status column 2024-02-06 15:42:31 +01:00
Andras Bacsai
3616fc8ca9 Refactor code and add additional destinations 2024-02-06 15:05:11 +01:00
Andras Bacsai
10e307f92b Refactor help button in navbar and boarding layout 2024-02-06 11:50:03 +01:00
Andras Bacsai
01f027ac1b Update version numbers to 4.0.0-beta.211 2024-02-06 11:41:49 +01:00
Andras Bacsai
dadc7aaf08 Merge pull request #1718 from coollabsio/next
v4.0.0-beta.210
2024-02-06 11:41:17 +01:00
Andras Bacsai
b96807d34c fix: feedback from self-hosted envs to discord 2024-02-06 11:36:20 +01:00
Andras Bacsai
45b736bb01 fix: stripe webhooks 2024-02-06 11:11:26 +01:00
Andras Bacsai
3d873a79a0 Merge pull request #1715 from coollabsio/next
fix: deploy issue with tag deployment
2024-02-06 07:21:33 +01:00
Andras Bacsai
6869c582ff Update retrieval of applications and services in Deploy controller 2024-02-06 07:21:06 +01:00
Andras Bacsai
9b9e5e939c Fix resource not found error and improve mass deployment process 2024-02-06 07:19:11 +01:00
Andras Bacsai
f626c15ecc Update version numbers + fix deploy issue 2024-02-06 07:12:09 +01:00
Andras Bacsai
8df1fe2e60 Merge pull request #1714 from coollabsio/next
Refactor database and service start commands
2024-02-05 20:58:16 +01:00
Andras Bacsai
fd2a533057 Refactor database and service start commands 2024-02-05 20:57:40 +01:00
Andras Bacsai
0a6401f990 Merge pull request #1713 from coollabsio/next
v4.0.0-beta.208
2024-02-05 20:24:44 +01:00
Andras Bacsai
1326fcb345 Add count checks for MySQL and MariaDB in isEmpty() method 2024-02-05 20:15:02 +01:00
Andras Bacsai
8b8e534598 Update version numbers to 4.0.0-beta.208 2024-02-05 19:53:14 +01:00
Andras Bacsai
0b518a3b76 Refactor code to load tags for environment applications and databases 2024-02-05 19:52:06 +01:00
RayBB
f357f40fc7 add syncthing template 2024-02-05 16:45:46 +01:00
Andras Bacsai
93fb14884e Merge pull request #1711 from coollabsio/next
Refactor server validation and installation logic
2024-02-05 15:13:56 +01:00
Andras Bacsai
26ccc4afb4 Refactor server validation and installation logic 2024-02-05 15:13:39 +01:00
Andras Bacsai
5fda1bb932 Merge pull request #1710 from coollabsio/next
v4.0.0-beta.207
2024-02-05 14:57:21 +01:00
Andras Bacsai
409ba8a1bb Refactor application deployment logic 2024-02-05 14:47:06 +01:00
Andras Bacsai
49f5240ff8 fix: better server validation and installation process
fix: add destination to queue deployment
feat: force start deployment
2024-02-05 14:40:54 +01:00
Andras Bacsai
0c3ed3d393 Update BunnyCDN sync and version numbers 2024-02-05 10:17:40 +01:00
Andras Bacsai
6e3dc474f2 Merge pull request #1702 from coollabsio/next
v4.0.0-beta.206
2024-02-05 10:06:59 +01:00
Andras Bacsai
d3eb87561e Fix styling issue in tag links 2024-02-05 10:00:53 +01:00
Andras Bacsai
8b58c8f856 Add tags to show and index views 2024-02-05 09:51:44 +01:00
Andras Bacsai
8c60ef5bd6 Update link in error message to the correct documentation 2024-02-04 17:00:13 +01:00
Andras Bacsai
1d59383c78 feat: clone to env 2024-02-04 16:54:12 +01:00
Andras Bacsai
60f590454d Update application deployment status in job handling 2024-02-04 14:40:23 +01:00
Andras Bacsai
dcb61a553e Merge pull request #1706 from piscis/patch-1
fix: Wrap tags and avoid horizontal overflow
2024-02-04 14:39:55 +01:00
Andras Bacsai
e06e31642f Refactor modal component and add new functionality 2024-02-04 14:07:08 +01:00
Andras Bacsai
9dfce48380 Add private_keys array initialization and define additional private properties 2024-02-04 13:50:24 +01:00
Andras Bacsai
8eed87e2f7 Update main class with mx-auto 2024-02-04 13:50:16 +01:00
Alex
d56d4eb8fc fix: Wrap tags and avoid horizontal overflow 2024-02-04 13:15:39 +01:00
Andras Bacsai
fd32cd04ab Refactor invoice payment failure handling in webhooks.php 2024-02-04 12:23:00 +01:00
Andras Bacsai
1d3b7ffd3b Refactor tags functionality and improve user experience 2024-02-03 12:44:18 +01:00
Andras Bacsai
0b5baf60a5 fix: tags 2024-02-03 12:39:07 +01:00
Andras Bacsai
bc31df6fb2 Update version numbers to 4.0.0-beta.206 2024-02-02 14:52:24 +01:00
Andras Bacsai
818399bc23 Merge pull request #1700 from coollabsio/next
v4.0.0-beta.205
2024-02-02 12:41:46 +01:00
Andras Bacsai
e7fdff0f69 feat: tags
ui: improvements
2024-02-02 11:50:28 +01:00
Andras Bacsai
6312c0ba84 feat: tags and tag deploy webhooks 2024-02-01 15:38:12 +01:00
Andras Bacsai
44efe0b5e1 Update versions and fix code formatting 2024-02-01 11:59:20 +01:00
Andras Bacsai
de7d584648 Merge pull request #1694 from coollabsio/next
v4.0.0-beta.204
2024-02-01 10:54:27 +01:00
Andras Bacsai
b9f12d2586 fix: duplicate domain check 2024-02-01 10:53:05 +01:00
Andras Bacsai
c76e8bb0de fix: migrate to new modal 2024-01-31 16:14:12 +01:00
Andras Bacsai
3b655f8e3f Update filebrowser image tag to use filebrowser/filebrowser:s6 2024-01-31 15:16:06 +01:00
Andras Bacsai
2b9df41444 fix: create dynamic directory 2024-01-31 15:04:08 +01:00
Andras Bacsai
628fec6904 fix: sentry error 2024-01-31 14:22:48 +01:00
Andras Bacsai
f36135cbfc fix: sentry 2024-01-31 14:20:57 +01:00
Andras Bacsai
75fe005055 fix: sentry error 2024-01-31 14:19:45 +01:00
Andras Bacsai
8ff7aeb78b ui: new modal component 2024-01-31 14:18:59 +01:00
Andras Bacsai
f1a9e28d5a fix: sentry 2024-01-31 14:18:51 +01:00
Andras Bacsai
843cd90ee5 Update exception type in generate_github_installation_token function 2024-01-31 13:47:16 +01:00
Andras Bacsai
1cbfd03912 fix: sentry fix 2024-01-31 13:46:40 +01:00
Andras Bacsai
ce60a39dc5 Throw RuntimeException instead of Exception when no resource is found in ScheduledTaskJob 2024-01-31 13:45:58 +01:00
Andras Bacsai
f1e4395a83 Refactor shared variable type validation 2024-01-31 13:43:23 +01:00
Andras Bacsai
52fd7ad571 fix: not able to use other shared envs 2024-01-31 13:40:15 +01:00
Andras Bacsai
5f797ec0ae Update version and release numbers 2024-01-31 10:28:18 +01:00
Andras Bacsai
21e77bf0c1 Merge pull request #1691 from coollabsio/next
v4.0.0-beta.203
2024-01-31 10:02:04 +01:00
Andras Bacsai
0686e48e89 fix: service deletion 2024-01-31 09:58:41 +01:00
Andras Bacsai
1cef233db2 fix: regenerate labels on application clone 2024-01-31 09:03:54 +01:00
Andras Bacsai
907e52572c fix: validate server navbar upated 2024-01-31 08:56:49 +01:00
Andras Bacsai
795c8abf64 Refactor service and resource deletion logic 2024-01-30 17:38:07 +01:00
Andras Bacsai
cc641d8cba revert 2024-01-30 17:16:52 +01:00
Andras Bacsai
d4668ef44a refactor 2024-01-30 14:12:40 +01:00
Andras Bacsai
e8b539c3bd Refactor CleanupUnreachableServers command to update server IP address 2024-01-30 10:53:12 +01:00
Andras Bacsai
6555f0b50c Fix unreachable server cleanup and enable daily schedule 2024-01-30 10:52:57 +01:00
Andras Bacsai
bb05058dda feat: cleanup unreachable servers 2024-01-30 10:50:54 +01:00
Andras Bacsai
9667cd4a7a fix: handle duplicate error instead of sql error
fix: set fqdns to null if you delete an app or a serviceapp
fix: make stucked resources a separate command
2024-01-30 09:48:51 +01:00
Andras Bacsai
3ae9501814 fix: dns validation + duplicated fqdns 2024-01-30 09:22:34 +01:00
Andras Bacsai
73d0948734 fix: service deletion fix 2024-01-30 08:26:33 +01:00
Andras Bacsai
c3e2a741ea Update version numbers 2024-01-29 20:35:35 +01:00
Andras Bacsai
4792146f1d Merge pull request #1687 from coollabsio/next
v4.0.0-beta.202
2024-01-29 16:37:39 +01:00
Andras Bacsai
09b9305aa3 Refactor git_clone_command in generateGitImportCommands function 2024-01-29 16:33:06 +01:00
Andras Bacsai
ff7d0d442d Update button text in private key create view 2024-01-29 16:33:01 +01:00
Andras Bacsai
9a127bdc80 Refactor webhook handling logic to remove duplicate code and improve readability 2024-01-29 16:31:10 +01:00
Andras Bacsai
919e88afb4 Refactor docker run options to compose format 2024-01-29 16:21:23 +01:00
Andras Bacsai
1d1ec20cb7 Update version numbers 2024-01-29 16:13:04 +01:00
Andras Bacsai
5c29ecdf10 feat: add initial support for custom docker run commands 2024-01-29 16:07:00 +01:00
Andras Bacsai
9e09c449cf fix: service deletion function 2024-01-29 16:03:45 +01:00
Andras Bacsai
09bcd693f5 Merge pull request #1683 from coollabsio/next
v4.0.0-beta.201
2024-01-29 14:04:30 +01:00
Andras Bacsai
0c15e45419 Update WordPress database configuration 2024-01-29 14:03:59 +01:00
Andras Bacsai
31cbf552a2 Merge pull request #1677 from mraf/wordpress-template
fix: add env variables for wordpress template without database
2024-01-29 13:58:31 +01:00
Andras Bacsai
f7853ee174 Refactor deployments_per_server variable and update dashboard view
This commit refactors the `deployments_per_server` variable in the `Dashboard` class to remove the type hint and updates the corresponding view file to handle the changes. The `deployments_per_server` variable is now grouped by `server_name` and converted to an array. This improves the organization and readability of the code.
2024-01-29 13:26:50 +01:00
Andras Bacsai
de3a7b6eca Add previous page functionality to deployment index
This commit adds the functionality to navigate to the previous page in the deployment index. It includes changes to the `Index.php` and `index.blade.php` files.
2024-01-29 13:06:26 +01:00
Andras Bacsai
b56c7c34cb fix: unhealthy deployments should be failed 2024-01-29 12:51:20 +01:00
Andras Bacsai
49845f3da7 fix: webhooks for multiple apps 2024-01-29 11:23:04 +01:00
Andras Bacsai
987409bae4 fix: bitbucket manual deployments 2024-01-29 10:43:18 +01:00
Andras Bacsai
07d8461f96 Fix URL encoding in deployment and status notifications 2024-01-29 08:49:05 +01:00
Andras Bacsai
f255a71434 Merge pull request #1673 from Niki2k1/feat/bitbucket-manual-webhook
feat: added manual webhook support for bitbucket
2024-01-29 08:38:05 +01:00
Andras Bacsai
2a2818ac0d Merge pull request #1680 from iamEvanYT/discord-deployment-url-fix
fix: encode project name in discord webhook notifications
2024-01-29 08:32:57 +01:00
Andras Bacsai
fd3cdc2c7d Update deployment status border color 2024-01-29 08:32:04 +01:00
Andras Bacsai
84c3f832ae Add missing closing div tag in dashboard.blade.php 2024-01-29 08:30:00 +01:00
Andras Bacsai
70c28fceeb fix: change env variable length 2024-01-29 08:24:21 +01:00
iamEvan
f2c4f83f5a Fix 2024-01-27 20:33:22 +00:00
iamEvan
c8dd6f07ac Encode Project Name 2024-01-27 20:22:27 +00:00
Andras Bacsai
561e424a7d feat: dashboard live deployment view 2024-01-27 18:44:40 +01:00
Andras Bacsai
c46d38907e fix: queue 2024-01-27 17:18:13 +01:00
Andras Bacsai
5c334bbac6 feat: add PR comments 2024-01-26 18:46:50 +01:00
Andras Bacsai
9628072b0c Update dependencies in package.json and mix-manifest.json 2024-01-26 11:36:44 +01:00
Andras Bacsai
39ecff9f90 Update version numbers 2024-01-26 11:34:18 +01:00
Andras Bacsai
d1daec060a Merge pull request #1675 from coollabsio/next
v4.0.0-beta.200
2024-01-26 11:25:32 +01:00
Andras Bacsai
fb5bea7f91 Update Docker actions versions 2024-01-26 11:23:49 +01:00
Andras Bacsai
efc3ea6e40 Update Docker actions versions 2024-01-26 11:20:27 +01:00
Andras Bacsai
ecefb9e1f5 Update vite version to 4.5.2 2024-01-26 11:15:35 +01:00
Andras Bacsai
a4dea2009a Remove unused imports and routes 2024-01-26 11:13:02 +01:00
Andras Bacsai
829e41f93f Delete TeamSharedVariablesIndexTest.php 2024-01-26 11:12:07 +01:00
Andras Bacsai
e7e3adc7fb Fix condition for checking localhost key in ProductionSeeder.php 2024-01-26 11:11:41 +01:00
Andras Bacsai
a993fef235 Update Docker build command 2024-01-26 10:56:22 +01:00
Andras Bacsai
81df71416c Update Docker images and add pull_policy 2024-01-26 10:52:58 +01:00
Andras Bacsai
40af7aa025 Update Docker build command to include latest tag 2024-01-26 10:46:46 +01:00
Andras Bacsai
050155328b Update toast.blade.php to use x-html instead of x-text 2024-01-26 10:37:53 +01:00
Andras Bacsai
376c081bed Add .env file as read-only volume 2024-01-26 10:37:33 +01:00
Andras Bacsai
9f5e1fa9e3 Update docker-compose.windows.yml file 2024-01-26 10:34:48 +01:00
Andras Bacsai
7d139fd33b Add environment file for Windows Docker Desktop 2024-01-26 10:31:37 +01:00
Andras Bacsai
b75a2857a0 Update environment variables and Docker image for Windows Docker Desktop 2024-01-26 10:24:42 +01:00
Andras Bacsai
ab6c1ddc20 Update docker-compose.windows.yml with bind mount for .env file 2024-01-26 10:16:44 +01:00
Andras Bacsai
dc0b0980a9 Update coolify image and environment variables 2024-01-26 09:57:59 +01:00
Andras Bacsai
788d1711db Refactor SSH command generation in remoteProcess.php 2024-01-26 09:36:08 +01:00
Andras Bacsai
d92dc4c5e6 Update testing-host image and remove unnecessary build configuration 2024-01-26 09:03:59 +01:00
Andras Bacsai
4bf34aea62 Add Coolify Testing Host workflow 2024-01-26 08:59:32 +01:00
Andras Bacsai
7e9a54ce67 Fix SSH command generation and disable mux in validateConnection() 2024-01-26 08:54:56 +01:00
Andras Bacsai
f8c19e1fb3 Update contact links in error and subscription views 2024-01-26 08:39:54 +01:00
Andras Bacsai
27c1bda09b Update button labels in create forms 2024-01-25 16:02:31 +01:00
Andras Bacsai
1af7ffcdc4 Refactor input field for port number 2024-01-25 15:58:58 +01:00
Andras Bacsai
39647367a5 Add build pack selection and update related fields 2024-01-25 15:57:04 +01:00
Andras Bacsai
ae4b263810 Update GitHub App registration button 2024-01-25 15:26:23 +01:00
Andras Bacsai
8901bb5df8 Refactor deployment cancellation and queue management 2024-01-25 13:45:17 +01:00
mraf
053aa25d2c fix: add env variables for wordpress template without database 2024-01-25 12:27:41 +01:00
Andras Bacsai
7a7157c155 fix: deployment queue
fix: cancel deployment
ui: changed to simpler toaster
2024-01-25 11:57:47 +01:00
Andras Bacsai
0c5e8600bd Update build pack settings and port values 2024-01-25 08:59:11 +01:00
Andras Bacsai
048e153025 Remove unnecessary condition in setDestination method 2024-01-25 08:57:16 +01:00
Andras Bacsai
e7cafe6850 fix: restrict concurrent deployments per server 2024-01-25 08:36:47 +01:00
Andras Bacsai
1385a86084 Refactor team() method and update references to team() in get_real_environment_variables() method 2024-01-24 15:56:43 +01:00
Andras Bacsai
348923ae02 Refactor realValue() method to include resource lookup 2024-01-24 15:54:55 +01:00
Andras Bacsai
7d754558b0 Fix branch selection and handle missing service 2024-01-24 12:26:14 +01:00
Andras Bacsai
744609e7e9 fix sentry errors 2024-01-24 12:10:03 +01:00
Andras Bacsai
9bd05b65a3 Update build pack and make GithubApp nullable 2024-01-24 12:07:58 +01:00
Andras Bacsai
d42934f258 fix: sentry error 2024-01-24 11:57:51 +01:00
Andras Bacsai
ff752e2411 feat: able to deploy multiple resources with webhook 2024-01-24 11:49:40 +01:00
Andras Bacsai
4120fba9a8 Update default concurrent_builds value to 2 2024-01-24 11:48:00 +01:00
Andras Bacsai
6ecb9c21ce cloud: send notification email if payment 2024-01-24 11:28:01 +01:00
Andras Bacsai
01f7b07fa3 feat: concurrent builds / server 2024-01-24 11:12:23 +01:00
Niklas Lausch
54d8cb9027 feat: added manual webhook support for bitbucket 2024-01-24 10:56:24 +01:00
Andras Bacsai
238337fecb Add new shared variable and update variable usage 2024-01-23 20:26:45 +01:00
Andras Bacsai
7a51acbf8d add slide-over component 2024-01-23 19:01:17 +01:00
Andras Bacsai
fb478c79b3 feat: shared environments 2024-01-23 17:13:23 +01:00
Andras Bacsai
abcc004953 feat: clone any resource 2024-01-22 16:08:18 +01:00
Andras Bacsai
2edf71a0dd feat: move resources between projects / environments 2024-01-22 15:12:38 +01:00
Andras Bacsai
cbec39099a Refactor Git section in advanced.blade.php 2024-01-22 14:13:40 +01:00
Andras Bacsai
dba5499182 Update version numbers + update glitchtip 2024-01-22 12:00:44 +01:00
Andras Bacsai
2fdf52929c Merge pull request #1670 from coollabsio/next
v4.0.0-beta.199
2024-01-22 11:02:30 +01:00
Andras Bacsai
8128dfc061 Update resource limits helper links 2024-01-22 10:47:47 +01:00
Andras Bacsai
2b394d6fea fix: show container on logs/executecontainer command views
fix: exclude containers with restart: no from hc
feat: add compose to predefined docker network
service: add glitchtip
2024-01-21 14:30:03 +01:00
Andras Bacsai
964ded1d0b fix: redis custom conf 2024-01-21 12:06:51 +01:00
Andras Bacsai
838c3830d6 Merge pull request #1664 from coollabsio/next
v4.0.0-beta.198
2024-01-18 15:06:12 +01:00
Andras Bacsai
2db93bd9b9 Add echo statement for queue cleanup and update cleanup message 2024-01-18 14:56:12 +01:00
Andras Bacsai
2f82dedd4f Add call to 'cleanup:queue' command in Init.php and remove 'cleanup:queue' command from Kernel.php 2024-01-18 14:49:47 +01:00
Andras Bacsai
8106602d15 Add CleanupQueue command to clean up Redis queue keys 2024-01-18 14:47:17 +01:00
Andras Bacsai
3d0bf6b472 Update resource name in notification messages 2024-01-18 14:03:51 +01:00
Andras Bacsai
ba7a7e9695 Update server and version configurations 2024-01-18 13:33:57 +01:00
Andras Bacsai
dd0ad04384 Merge pull request #1662 from coollabsio/next
Do not report server is not ready
2024-01-18 12:45:05 +01:00
Andras Bacsai
910a1f43a9 Do not report server is not ready 2024-01-18 12:44:20 +01:00
Andras Bacsai
e2f959ce4c Merge pull request #1659 from coollabsio/next
v4.0.0-beta.197
2024-01-18 12:14:50 +01:00
Andras Bacsai
68fe886fb0 Add restart functionality to proxy deployment 2024-01-18 12:14:11 +01:00
Andras Bacsai
4631c73809 Update general.blade.php with build server option and network section 2024-01-18 12:05:48 +01:00
Andras Bacsai
77558b37da refactor build server 2024-01-18 11:40:13 +01:00
Andras Bacsai
e060409a76 fix: links 2024-01-18 11:24:07 +01:00
Andras Bacsai
af01bc3e77 fix: service deletion bug! 2024-01-17 15:48:01 +01:00
Andras Bacsai
1e158badfc Update button text in by-ip.blade.php 2024-01-17 15:41:38 +01:00
Andras Bacsai
c620bb58ed Add build pack selection and show/hide static site options 2024-01-17 15:41:32 +01:00
Andras Bacsai
8a91395472 Update server model to use 'coolify' instead of 'coolify-overlay' for the name field 2024-01-17 14:11:46 +01:00
Andras Bacsai
c5f3398b73 Update build server and swarm support messages 2024-01-17 14:06:41 +01:00
Andras Bacsai
3878527de8 Update server unreachable notifications to include automatic revival 2024-01-17 14:02:54 +01:00
Andras Bacsai
4abcb2d5b9 Update notification labels in Discord and Telegram settings 2024-01-17 12:23:58 +01:00
Andras Bacsai
a635e51486 fix: server status job 2024-01-17 11:52:56 +01:00
Andras Bacsai
b6ce2e9122 typo 2024-01-17 11:39:45 +01:00
Andras Bacsai
8c60dd5523 typo 2024-01-17 11:21:47 +01:00
Andras Bacsai
94e2d951c4 Revert commented out code and execute remote command to remove Docker container 2024-01-16 15:45:54 +01:00
Andras Bacsai
381e24bea5 fix: git pull command for deploy key based previews 2024-01-16 15:45:19 +01:00
Andras Bacsai
2b1e35980f empty nixpacks type result in error 2024-01-16 15:26:44 +01:00
Andras Bacsai
a42c8da344 fix: proxy ui view
feat: build server 🌮
2024-01-16 15:19:14 +01:00
Andras Bacsai
7a0e415ecf fix: checkbox click 2024-01-16 12:28:59 +01:00
Andras Bacsai
d721f4809a fix ui 2024-01-16 12:20:40 +01:00
Andras Bacsai
22431eee9a fix: change proxy view 2024-01-16 11:32:56 +01:00
Andras Bacsai
c058c0a766 Update release version to 4.0.0-beta.197 2024-01-16 08:38:39 +01:00
Andras Bacsai
1724c0d3ff Merge pull request #1658 from coollabsio/next
v4.0.0-beta.196
2024-01-15 20:22:57 +01:00
Andras Bacsai
0b8f48230f Remove unnecessary echo statements in Server.php 2024-01-15 20:22:13 +01:00
Andras Bacsai
e8d84b7067 Update server and version configurations 2024-01-15 19:57:29 +01:00
Andras Bacsai
5236bbc757 Merge pull request #1655 from coollabsio/next
v4.0.0-beta.195
2024-01-15 14:30:43 +01:00
Andras Bacsai
094e1d1bba Fix condition for merge_request actions in webhooks.php 2024-01-15 14:29:54 +01:00
Andras Bacsai
68b25523d6 Fix action condition in merge_request webhook handler 2024-01-15 14:27:45 +01:00
Andras Bacsai
bdc478d5f5 Update daisyui version in package.json and package-lock.json 2024-01-15 13:46:38 +01:00
Andras Bacsai
002472d7c6 Update version numbers and dependencies 2024-01-15 13:40:32 +01:00
Andras Bacsai
0d65bf62b9 Merge pull request #1654 from coollabsio/next
v4.0.0-beta.194
2024-01-15 13:24:01 +01:00
Andras Bacsai
01c7e76071 only add gzip + https redirect once 2024-01-15 13:23:28 +01:00
Andras Bacsai
884ae0efb0 Merge pull request #1653 from coollabsio/next
v4.0.0-beta.193
2024-01-15 13:03:40 +01:00
Andras Bacsai
8e7040bf7c Update Docker Compose commands to include SOURCE_COMMIT environment variable 2024-01-15 12:59:21 +01:00
Andras Bacsai
059e6a88eb Replace comma with pipe in customLabels 2024-01-15 12:30:49 +01:00
Andras Bacsai
9947158f7e fix gzip compression 2024-01-15 12:12:34 +01:00
Andras Bacsai
61aa9e8766 Merge pull request #1651 from coollabsio/next
v4.0.0-beta.192
2024-01-15 11:53:34 +01:00
Andras Bacsai
75813a289c check domains againts cloudflare ip range 2024-01-15 11:37:26 +01:00
Andras Bacsai
af11d8cf3d Merge pull request #1650 from coollabsio/next
v4.0.0-beta.191
2024-01-15 11:06:15 +01:00
Andras Bacsai
48990db699 Add link to documentation for further help 2024-01-15 11:00:09 +01:00
Andras Bacsai
da71353bfa Update application and proxy configuration 2024-01-15 10:49:39 +01:00
Andras Bacsai
0f5559bc61 fix: service stack view 2024-01-15 10:19:37 +01:00
Andras Bacsai
1afb509c33 add domain validation + custom dns servers
add new guides / docs
2024-01-15 10:03:15 +01:00
Andras Bacsai
bccca6e874 add docs to server validation 2024-01-15 09:03:46 +01:00
Andras Bacsai
083dc15053 Update version and add OpenSSH server detection and PermitRootLogin check 2024-01-15 08:40:46 +01:00
Andras Bacsai
1b6d376472 refactor: compose file and install script 2024-01-12 21:26:51 +01:00
Andras Bacsai
891deee05a Update version numbers to 4.0.0-beta.191 2024-01-12 15:42:05 +01:00
Andras Bacsai
5ffbba908b Refactor healthcheck test in StartPostgresql.php 2024-01-12 15:41:49 +01:00
Andras Bacsai
f762959c9f Refactor select.blade.php file 2024-01-12 15:41:46 +01:00
Andras Bacsai
90a5a23fd9 Fix initial username placeholder in PostgreSQL database view 2024-01-12 15:41:43 +01:00
Andras Bacsai
94e87141ff Merge pull request #1639 from coollabsio/next
Fix resource limits for CPU set
2024-01-12 14:37:34 +01:00
Andras Bacsai
fceaf3e94b Fix resource limits for CPU set 2024-01-12 14:30:25 +01:00
Andras Bacsai
3be554cb55 Merge pull request #1638 from coollabsio/next
v4.0.0-beta.190
2024-01-12 14:20:13 +01:00
Andras Bacsai
27b18fbedf Refactor database and application start scripts 2024-01-12 14:15:15 +01:00
Andras Bacsai
5e7c6906b3 fix: cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. 2024-01-12 13:47:01 +01:00
Andras Bacsai
d05ffe32a3 Merge pull request #1630 from coollabsio/next
v4.0.0-beta.189
2024-01-12 12:51:00 +01:00
Andras Bacsai
f1298d1db4 do not send notification of scheduled task failed 2024-01-12 12:44:08 +01:00
Andras Bacsai
408738e08d Add service status to Index.php and update resource display in index.blade.php 2024-01-12 11:31:51 +01:00
Andras Bacsai
8d04fbdb74 feat: search between resources 2024-01-12 11:25:20 +01:00
Andras Bacsai
dccb31d17e fix: service deletion command 2024-01-12 09:11:36 +01:00
Andras Bacsai
f61210287e Update release version to 4.0.0-beta.189 2024-01-12 08:56:59 +01:00
Andras Bacsai
18ad7220f0 10 mins server check -> 5 mins 2024-01-12 08:49:03 +01:00
Andras Bacsai
79e0df1d43 fix: cleanup docker stuffs before upgrading 2024-01-12 08:45:24 +01:00
Andras Bacsai
a2f53085e5 fix: preview deployments with nixpacks 2024-01-12 08:38:08 +01:00
Andras Bacsai
c5782252ea Merge branch 'fix' into next 2024-01-11 19:00:13 +01:00
Andras Bacsai
bf3d88facd Merge pull request #1632 from coollabsio/fix
v4.0.0-beta.188
2024-01-11 18:59:23 +01:00
Andras Bacsai
e45b0bf715 Update version numbers to 4.0.0-beta.188 2024-01-11 18:55:20 +01:00
Andras Bacsai
9db6c12eea fix missing a end tag 2024-01-11 18:54:55 +01:00
Andras Bacsai
3a391b69e8 fix: restart should not update config hash 2024-01-11 14:34:48 +01:00
Andras Bacsai
cc1fb83c79 cleanup 2024-01-11 14:25:55 +01:00
Andras Bacsai
efa5dd28f1 fix: load profile and set envs on remote cmd 2024-01-11 14:25:42 +01:00
Andras Bacsai
f1eddae379 cleanup 2024-01-11 14:24:54 +01:00
Andras Bacsai
34febe670d fix: load profile on remote commands 2024-01-11 14:13:43 +01:00
Andras Bacsai
3137131a1a Merge pull request #1628 from coollabsio/next
v4.0.0-beta.187
2024-01-11 12:57:01 +01:00
Andras Bacsai
1b6546d26c fix: nixpacks envs
fix: append logs
2024-01-11 12:56:02 +01:00
Andras Bacsai
b9f820cef4 fix: save cmd output propely (merge) 2024-01-11 12:33:28 +01:00
Andras Bacsai
d8639f58d7 Merge pull request #1625 from coollabsio/next
v4.0.0-beta.186
2024-01-11 11:56:00 +01:00
Andras Bacsai
cf9be9355f fix: upload limit on ui 2024-01-11 11:55:17 +01:00
Andras Bacsai
e36bb11ba8 fix: undead endpoint 2024-01-11 11:34:05 +01:00
Andras Bacsai
190beb3d3f fix: escape build envs properly for nixpacks + docker build 2024-01-11 11:32:32 +01:00
Andras Bacsai
890a6925d1 fix: email verification / forgot password 2024-01-11 08:52:30 +01:00
Andras Bacsai
d03b8420f8 Merge pull request #1627 from coollabsio/fix
fix: Email verification and forgot-password
2024-01-11 08:47:05 +01:00
Andras Bacsai
cbd3c880c3 Add email verification and password reset functionality 2024-01-11 07:16:28 +01:00
Andras Bacsai
6b24001876 feat: import backups 2024-01-10 15:42:54 +01:00
Andras Bacsai
6bb45430c9 fix: submit error on postgresql 2024-01-10 14:41:05 +01:00
Andras Bacsai
bc6b4ed850 Merge branch 'stooit-feat/db-import' into next 2024-01-10 14:37:30 +01:00
Andras Bacsai
8a63ef5da9 Merge branch 'feat/db-import' of github.com:stooit/coolify into stooit-feat/db-import 2024-01-10 14:36:59 +01:00
Andras Bacsai
e324866a27 Refactor database configuration layout 2024-01-10 14:34:04 +01:00
Andras Bacsai
0e5f733657 Add import tab to database configuration view 2024-01-10 14:33:21 +01:00
Andras Bacsai
c5932ed337 smaller ui fixes 2024-01-10 12:41:29 +01:00
Andras Bacsai
ef428f844f fix: do not include thegameplan.json into build image 2024-01-10 12:26:27 +01:00
Andras Bacsai
eb8b752a6e Merge pull request #1607 from stooit/feat/scheduled-tasks-cron
feat: Scheduled tasks (cron)
2024-01-10 12:08:47 +01:00
Andras Bacsai
ce0b38035c Merge branch 'next' into feat/scheduled-tasks-cron 2024-01-10 12:03:48 +01:00
Andras Bacsai
562a8f1fac Merge pull request #1614 from RayBB/fix-typos
fix typos
2024-01-10 11:56:29 +01:00
Andras Bacsai
68f1621757 Merge branch 'main' into fix-typos 2024-01-10 11:55:33 +01:00
Andras Bacsai
e3087573bb Merge pull request #1618 from Illyism/patch-1
Update README.md
2024-01-10 11:52:52 +01:00
Andras Bacsai
7869f223a3 Update version numbers to 4.0.0-beta.186 2024-01-10 11:52:39 +01:00
Andras Bacsai
0e99f27108 Merge pull request #1621 from coollabsio/next
v4.0.0-beta.185
2024-01-10 11:09:41 +01:00
Andras Bacsai
f445a8c312 fix: update navbar on build_pack change 2024-01-10 11:07:53 +01:00
Andras Bacsai
845fc191d4 Remove ray debug statement in updatedApplicationBuildPack method 2024-01-10 11:00:11 +01:00
Andras Bacsai
95d0d72e0d Fix application build pack logic 2024-01-10 11:00:06 +01:00
Andras Bacsai
76f695036c fix: static buildpack should set port 80 2024-01-10 10:58:31 +01:00
Andras Bacsai
225bf06736 Update version numbers and remove unused files 2024-01-10 10:50:07 +01:00
Andras Bacsai
3a287ae974 Update Dockerfile to use serversideup/php:8.2-fpm-nginx image 2024-01-10 09:25:04 +01:00
Andras Bacsai
e5c61b9f9f Merge pull request #1620 from coollabsio/next
fix: fix php-pgsql version to 8.2
2024-01-09 19:05:46 +01:00
Andras Bacsai
32bc876dfc fix: php pgsql to 8.2 2024-01-09 19:04:12 +01:00
Andras Bacsai
c9b3d2a43d Update PHP version in Dockerfiles 2024-01-09 15:23:37 +01:00
Andras Bacsai
f343210e7c hmm 2024-01-09 15:22:06 +01:00
Andras Bacsai
4a42bff0dc Update version numbers to 4.0.0-beta.185 2024-01-09 15:18:33 +01:00
Andras Bacsai
36931b5b18 Merge pull request #1612 from coollabsio/next
v4.0.0-beta.184
2024-01-09 14:58:01 +01:00
Andras Bacsai
3b080abada remove rays 2024-01-09 14:48:26 +01:00
Andras Bacsai
7feba4bbaa fix: remove traefik debug in dev mode 2024-01-09 14:27:55 +01:00
Andras Bacsai
eef8c756df fix: settings menu 2024-01-09 14:27:42 +01:00
Andras Bacsai
9ed30cb0dc Remove unnecessary proxy configurations 2024-01-09 13:13:55 +01:00
Andras Bacsai
6bc43bd999 fix: sort and rename (unique part) of labels 2024-01-09 13:00:07 +01:00
Andras Bacsai
e7683ee9a5 fix: service labels without ports (unknown ports) 2024-01-09 12:49:03 +01:00
Andras Bacsai
ee71aeaa36 fix: use ip for sslip in dev if remote server is used 2024-01-09 12:48:46 +01:00
Andras Bacsai
404c664500 fix: traefik labels 2024-01-09 12:29:45 +01:00
Andras Bacsai
aa80392b46 small fix 2024-01-09 12:25:50 +01:00
Andras Bacsai
c9509ef658 fix: show framework based notification in build logs 2024-01-09 12:19:39 +01:00
Andras Bacsai
31e08a24c9 fix: healthy status 2024-01-09 08:42:37 +01:00
Ilias Ism
14b32e30cd Update README.md 2024-01-08 23:47:11 +01:00
Raymond Berger
5aaad66fe5 fix data naming conflict 2024-01-08 21:59:26 +01:00
Andras Bacsai
b6745c691b fix: queue retry
fix: nixpacks builds
2024-01-08 16:33:34 +01:00
Ray Berger
5ee29c6072 fix typos 2024-01-07 22:32:54 +00:00
Andras Bacsai
b69584fe26 Merge branch 'main' into next 2024-01-07 16:25:13 +01:00
Andras Bacsai
4c3907c296 refactor routes 2024-01-07 16:23:41 +01:00
Andras Bacsai
bf44b4b949 version++ 2024-01-07 13:34:47 +01:00
Andras Bacsai
bee7a2357b Merge pull request #1608 from RayBB/patch-1
fix small typo
2024-01-07 13:34:11 +01:00
Andras Bacsai
98704fc3c2 Merge pull request #1605 from coollabsio/next
v4.0.0-beta.183
2024-01-06 11:39:51 +01:00
Andras Bacsai
e286e78ddc fix: database env variables 2024-01-06 11:38:12 +01:00
Stuart Rowlands
557e1407d0 Added database import feature. 2024-01-06 15:24:57 +10:00
Raymond Berger
3c99f24b5a fix small typo 2024-01-05 15:10:30 +01:00
Stuart Rowlands
512197021b Minor cleanup. 2024-01-05 15:26:51 +10:00
Stuart Rowlands
e233ec05b5 Merge branch 'main' into feat/scheduled-tasks-cron 2024-01-05 15:08:20 +10:00
Stuart Rowlands
d0e3a20a65 Merge branch 'main' of github.com:stooit/coolify 2024-01-05 15:08:06 +10:00
Stuart Rowlands
e2e6813632 Functional scheduled executions.
Display last executions.
Support for Services.
2024-01-05 15:06:36 +10:00
Andras Bacsai
963c519c38 feat: add www-non-www redirects to traefik 2024-01-04 14:14:40 +01:00
Andras Bacsai
d04513d817 Merge pull request #1603 from coollabsio/next
v4.0.0-beta.181
2024-01-04 13:08:18 +01:00
Andras Bacsai
64a7f27e37 fix: file storage save 2024-01-04 13:03:46 +01:00
Andras Bacsai
65652142b2 Merge pull request #1599 from coollabsio/next
v4.0.0-beta.181
2024-01-03 21:26:44 +01:00
Andras Bacsai
7691319c59 Refactor Dockerfile copying logic in ApplicationDeploymentJob.php 2024-01-03 21:15:58 +01:00
Andras Bacsai
206bd50d00 Update nixpacks build command in ApplicationDeploymentJob.php 2024-01-03 21:14:02 +01:00
Andras Bacsai
6159783a73 fix: nixpacks buildpack 2024-01-03 21:03:46 +01:00
Andras Bacsai
ed5f831c86 Merge pull request #1598 from coollabsio/next
v4.0.0-beta.180
2024-01-03 13:42:29 +01:00
Andras Bacsai
65be83e75d fix: only add restart policy if its empty (compose) 2024-01-03 13:37:14 +01:00
Andras Bacsai
25a471b045 Update Dockerfile with new versions of PACK and NIXPACKS 2024-01-03 13:29:23 +01:00
Andras Bacsai
60c7a29aa8 fix: nixpacks cache 2024-01-03 13:20:24 +01:00
Andras Bacsai
11ab6669a0 Merge pull request #1597 from coollabsio/next
v4.0.0-beta.179
2024-01-02 21:13:34 +01:00
Andras Bacsai
53965ab8ed fix: set deployment failed if new container is not healthy 2024-01-02 21:07:16 +01:00
Andras Bacsai
ea271ca079 fix: delete serviceApps & serviceDatabases
fix: cleanup trashed data
2024-01-02 21:03:51 +01:00
Andras Bacsai
f16d0f650f Merge pull request #1596 from coollabsio/next
v4.0.0-beta.178
2024-01-02 17:23:11 +01:00
Andras Bacsai
cb80341a78 Fix customLabels assignment when proxyType is TRAEFIK_V2 2024-01-02 17:22:44 +01:00
Andras Bacsai
83d96c8d11 Refactor custom labels handling in General.php and update Docker Compose Content label in general.blade.php 2024-01-02 17:14:52 +01:00
Andras Bacsai
a8ca57d095 Update link in helper message in general.blade.php 2024-01-02 16:47:05 +01:00
Andras Bacsai
2d936a4b22 add compose link 2024-01-02 16:46:08 +01:00
Andras Bacsai
0653eb8511 set custom labels on every app 2024-01-02 16:44:41 +01:00
Andras Bacsai
cc64132627 Update README.md 2024-01-02 14:06:08 +01:00
Andras Bacsai
e0391e5abd Merge pull request #1594 from coollabsio/next
v4.0.0-beta.177
2024-01-02 13:58:30 +01:00
Andras Bacsai
025135bd2a feat: raw docker compose deployments 2024-01-02 13:55:35 +01:00
Andras Bacsai
5e5873a08d fix: duplicate compose variable 2024-01-02 11:46:02 +01:00
Andras Bacsai
14652c2878 Update sponsor logos and links 2024-01-02 07:31:54 +01:00
Stuart Rowlands
9bbe9567c7 Start scheduled task job execution. 2024-01-01 18:23:58 -08:00
Stuart Rowlands
7913a639b5 Complete add/edit/delete for scheduled tasks.
Refactor views.
2024-01-01 18:23:29 -08:00
Stuart Rowlands
adecf328fc WIP start of scheduled tasks. 2024-01-01 10:33:16 -08:00
Andras Bacsai
a7ef5c456d Merge pull request #1592 from coollabsio/next
v4.0.0-beta.176
2023-12-31 10:52:15 +01:00
Andras Bacsai
117f1d4155 fix: horizon 2023-12-30 15:30:57 +01:00
Andras Bacsai
075f3ce930 remove unnecessary queue 2023-12-30 15:29:26 +01:00
Andras Bacsai
d5b3e88fc4 Merge pull request #1576 from coollabsio/next
v4.0.0-beta.175
2023-12-30 15:00:05 +01:00
Andras Bacsai
ba55e0c1bb feat: add environment description + able to change name 2023-12-30 14:47:26 +01:00
Andras Bacsai
54671354f0 fix: deploy key + docker compose 2023-12-30 14:20:02 +01:00
Andras Bacsai
a2c7e8d455 Merge pull request #1566 from stooit/fix/deploy-key-docker-compose
fix: Resolves deployment of docker compose applications when using a deploy key
2023-12-30 13:27:25 +01:00
Andras Bacsai
32dbdf5204 Fix redirect route in DecideWhatToDoWithUser middleware 2023-12-28 22:26:21 +01:00
Andras Bacsai
019887739c fix: wrong env variable parsing 2023-12-28 17:53:47 +01:00
Andras Bacsai
5596e41f2b fix: sub 2023-12-28 13:43:03 +01:00
Andras Bacsai
6a73a00a1f Merge pull request #1575 from coollabsio/next
v4.0.0-beta.174
2023-12-27 23:09:14 +01:00
Andras Bacsai
4121c9dd8b fix 2023-12-27 23:07:39 +01:00
Andras Bacsai
e4781dc129 fix: restore falsely deleted coolify-db-backup 2023-12-27 23:06:22 +01:00
Andras Bacsai
1c90f46f2a Merge pull request #1572 from coollabsio/next
v4.0.0-beta.173
2023-12-27 17:44:07 +01:00
Andras Bacsai
e78d851c85 fix: button title 2023-12-27 17:14:18 +01:00
Andras Bacsai
52d05005ed fix: deploy instead of restart in case swarm is used 2023-12-27 17:12:09 +01:00
Andras Bacsai
f03aa57758 fix: routing, switch back to old one 2023-12-27 16:45:01 +01:00
Andras Bacsai
8c20c833ba fix: add source commit to final envs 2023-12-27 13:06:59 +01:00
Andras Bacsai
2fe6766b7f fix: cpu limit to float from int 2023-12-27 13:01:57 +01:00
Stuart Rowlands
d9599da4a8 Fix git clone command for deploy key + docker compose. 2023-12-21 11:16:03 -08:00
Andras Bacsai
3f453ba7c0 Merge branch 'main' into next 2023-12-21 14:09:13 +01:00
Andras Bacsai
bd02c3055a update readme 2023-12-21 14:08:39 +01:00
andrasbacsai
7ea7d85d15 Deploying to main from @ coollabsio/coolify@d38350c282 🚀 2023-12-21 12:58:10 +00:00
Andras Bacsai
d38350c282 update 2023-12-21 13:57:52 +01:00
andrasbacsai
a83c70004c Deploying to main from @ coollabsio/coolify@49e1404a2c 🚀 2023-12-21 12:56:00 +00:00
Andras Bacsai
49e1404a2c test gh auto sponsor update 2023-12-21 13:55:40 +01:00
Andras Bacsai
76f23e7dbf do not check server ready on server status job 2023-12-21 10:43:39 +01:00
Andras Bacsai
ad8653f54d Merge pull request #1564 from coollabsio/next
v4.0.0-beta.172
2023-12-21 10:35:40 +01:00
Andras Bacsai
8939d77051 fix 2023-12-21 10:28:02 +01:00
Andras Bacsai
37be4a1796 fix 2023-12-21 10:00:41 +01:00
Andras Bacsai
e4c923e358 fix 2023-12-21 09:57:39 +01:00
Andras Bacsai
62ca3ffaa5 fix 2023-12-21 09:55:16 +01:00
Andras Bacsai
9af3ce4be5 fail job instead of runtime exception 2023-12-21 09:49:18 +01:00
Andras Bacsai
fe143ef8a5 Merge pull request #1563 from coollabsio/next
v4.0.0-beta.171
2023-12-21 09:35:43 +01:00
Andras Bacsai
5fb5845e90 redirect false on some urls 2023-12-21 09:33:11 +01:00
Andras Bacsai
794cfbd8eb execute handle on containerstatusjob 2023-12-21 09:28:47 +01:00
Andras Bacsai
29ee9915f3 fix: check proxy after mount on server view
fix: change realtime console log
version++
2023-12-21 09:28:39 +01:00
Andras Bacsai
331d485213 Merge pull request #1562 from coollabsio/next
v4.0.0-beta.170
2023-12-21 08:49:22 +01:00
Andras Bacsai
665e3761c4 fix: stay tuned 2023-12-21 08:48:53 +01:00
Andras Bacsai
ac19f0e34f enable docker image swarms 2023-12-21 08:46:48 +01:00
Andras Bacsai
d7cfa0578f Comment out handle() method call in
ContainerStatusJob constructor
2023-12-20 20:01:45 +01:00
Andras Bacsai
694169bb84 fix: why?! 2023-12-20 18:09:01 +01:00
Andras Bacsai
ab853cac87 Merge pull request #1560 from coollabsio/next
v4.0.0-beta.169
2023-12-20 16:21:06 +01:00
Andras Bacsai
51db2f797d fix: storage error on dbs 2023-12-20 16:19:48 +01:00
Andras Bacsai
0c90d3d0a1 fix: docker compose apps env rewritten 2023-12-20 16:15:13 +01:00
Andras Bacsai
51efe23690 Merge pull request #1559 from coollabsio/next
disable db + service deployments swarm
2023-12-20 14:46:12 +01:00
Andras Bacsai
e3ee84105c disable db + service deployments swarm 2023-12-20 14:45:47 +01:00
Andras Bacsai
6cbd61ac6c Merge pull request #1558 from coollabsio/next
fix: get swarm service logs
2023-12-20 14:25:53 +01:00
Andras Bacsai
638d0c8c99 fix: get swarm service logs 2023-12-20 14:11:50 +01:00
Andras Bacsai
aecc81fe9d Merge pull request #1557 from coollabsio/next
v4.0.0-beta.166
2023-12-20 13:21:45 +01:00
Andras Bacsai
c9a1437870 Fix handle method in ServerStatusJob 2023-12-20 12:33:58 +01:00
Andras Bacsai
66b41b3d4c Update ServerStatusJob middleware and uniqueId()
method
2023-12-20 12:33:21 +01:00
Andras Bacsai
c41cfe2a2f Fix server status check and cleanup logic 2023-12-20 12:32:46 +01:00
Andras Bacsai
5f2ad56529 Update container and server status job 2023-12-20 12:25:14 +01:00
Andras Bacsai
cd842bc1b2 Update number of tries in ContainerStatusJob 2023-12-20 12:13:34 +01:00
Andras Bacsai
27b6aad53a fix 2023-12-20 11:59:53 +01:00
Andras Bacsai
64b58b7661 hm 2023-12-20 11:59:06 +01:00
Andras Bacsai
94960d96a9 add max horizon processes 2023-12-20 11:47:51 +01:00
Andras Bacsai
2549244f97 hm 2023-12-20 11:44:46 +01:00
Andras Bacsai
5bfffce33b hm 2023-12-20 11:37:04 +01:00
Andras Bacsai
3a4f19f368 version++ 2023-12-20 11:22:10 +01:00
Andras Bacsai
50e17ed932 fix: server ready 2023-12-20 11:21:17 +01:00
Andras Bacsai
a8fcd7aee4 Merge pull request #1556 from coollabsio/next
small UI fixes
2023-12-20 10:31:59 +01:00
Andras Bacsai
87036cc49b link 2023-12-20 10:27:39 +01:00
Andras Bacsai
e48842c6ec fix: swarm support ui 2023-12-20 10:19:21 +01:00
Andras Bacsai
b9e405c497 Merge pull request #1555 from coollabsio/next
v4.0.0-beta.165
2023-12-19 21:01:40 +01:00
Andras Bacsai
f75effe022 fix 2023-12-19 15:44:41 +01:00
Andras Bacsai
a745f568f3 gh actions update 2023-12-19 15:44:01 +01:00
Andras Bacsai
e2e3ad0358 get branchname gh actions 2023-12-19 15:41:53 +01:00
Andras Bacsai
ba769f5fb7 fix 2023-12-19 15:36:59 +01:00
Andras Bacsai
0126286731 fix: server update schedule 2023-12-19 15:16:08 +01:00
Andras Bacsai
7952202435 fix: do not autovalidate server on mount 2023-12-19 14:19:23 +01:00
Andras Bacsai
798acb8ee5 add swarm server grouping
fixes for swarm
2023-12-19 13:47:12 +01:00
Andras Bacsai
ef595dd4c2 fix: server not found 2023-12-19 12:24:43 +01:00
Andras Bacsai
70c662daf8 disable swarm for the next release 2023-12-18 17:13:22 +01:00
Andras Bacsai
8ae385b9f9 fix: add alpha to swarm 2023-12-18 14:34:04 +01:00
Andras Bacsai
802a0f7684 fix: do not push dockerimage 2023-12-18 14:17:48 +01:00
Andras Bacsai
62c38c9859 wip: swarm 2023-12-18 14:01:25 +01:00
Andras Bacsai
27c36bec83 feat: custom docker compose commands 2023-12-17 20:56:12 +01:00
Andras Bacsai
c6b8eabe10 wip: swarm 2023-12-15 15:48:01 +01:00
Andras Bacsai
967fca9eca version ++ 2023-12-15 15:17:49 +01:00
Andras Bacsai
40a239ddda Merge pull request #1550 from coollabsio/next
refactor: gitlab manual webhooks
2023-12-15 14:19:49 +01:00
Andras Bacsai
99d07981cf fix 2023-12-15 14:19:29 +01:00
Andras Bacsai
b3ee6b7144 fix: add debug output to gitlab webhooks 2023-12-15 14:17:53 +01:00
Andras Bacsai
468ad7d904 fix: no action in webhooks 2023-12-15 14:09:14 +01:00
Andras Bacsai
f1aa97e374 Merge pull request #1548 from coollabsio/next
fix: compose domains & links
2023-12-15 11:01:03 +01:00
Andras Bacsai
3b6d3343c7 fix: domains for compose bp 2023-12-15 11:00:51 +01:00
Andras Bacsai
ab2f9f073f Merge pull request #1546 from stooit/fix/docker-compose-multidomain
fix: Multiple domain links in docker compose applications.
2023-12-15 10:39:01 +01:00
Andras Bacsai
67131152cc fix: reset domains on compose file change 2023-12-15 10:37:45 +01:00
Andras Bacsai
3bda289428 fix: ui for adding new destination 2023-12-15 10:24:02 +01:00
Andras Bacsai
fadfa0ad8e Merge pull request #1547 from coollabsio/next
fix: server checking status
2023-12-15 10:01:58 +01:00
Andras Bacsai
11a957c6c9 fix: server checking status 2023-12-15 10:01:14 +01:00
Stuart Rowlands
b46de99af9 Fixes multi-domain links in docker compose applications. 2023-12-14 18:16:17 -08:00
Andras Bacsai
03420076c9 Merge pull request #1545 from coollabsio/next
v4.0.0-beta.163
2023-12-14 15:41:16 +01:00
Andras Bacsai
549446abdf fix: handle other types of generated values 2023-12-14 15:34:05 +01:00
Andras Bacsai
06ab2145ca fix: improve server status check times 2023-12-14 15:33:46 +01:00
Andras Bacsai
5d088e530e version++ 2023-12-14 15:33:34 +01:00
Andras Bacsai
123e6eddd7 fix: only check server status in container status job 2023-12-14 15:33:25 +01:00
Andras Bacsai
2c17e431ac Merge pull request #1544 from coollabsio/next
fix: backup executions view
2023-12-14 15:01:35 +01:00
Andras Bacsai
fe6073ba7d fix: backup executions view 2023-12-14 15:01:19 +01:00
Andras Bacsai
8a9ad04744 Merge pull request #1540 from coollabsio/next
v4.0.0-beta.162
2023-12-14 14:58:30 +01:00
Andras Bacsai
75d1ec4f42 feat: pull latest images for services 2023-12-14 14:50:38 +01:00
Andras Bacsai
a0abde8652 ui: add image name to service stack + better options visibility 2023-12-14 14:24:54 +01:00
Andras Bacsai
db13dd9304 fix: revert random container job delay 2023-12-13 15:40:57 +01:00
Andras Bacsai
638bcf9732 update 2023-12-13 15:34:33 +01:00
Andras Bacsai
b06b465ffa fix: add catch all route 2023-12-13 15:29:45 +01:00
Andras Bacsai
02c8b9f471 fix: password reset / invitation link requests 2023-12-13 15:22:37 +01:00
Andras Bacsai
1ff1664b6c fix: copy invitation 2023-12-13 14:44:11 +01:00
Andras Bacsai
52d84c5e9e refactor: clone project 2023-12-13 14:22:23 +01:00
Andras Bacsai
e0289e2949 feat: randomly sleep between executions 2023-12-13 12:35:56 +01:00
Andras Bacsai
ff8d8371ad fix: check queued deployments as well 2023-12-13 12:13:20 +01:00
Andras Bacsai
69343f974a soft delete models 2023-12-13 12:08:12 +01:00
Andras Bacsai
2dc175be63 fix: null notify 2023-12-13 12:01:27 +01:00
Andras Bacsai
d93bf97919 cleanup on start 2023-12-13 12:01:21 +01:00
Andras Bacsai
4ea8916d53 fix: update Coolify script 2023-12-13 11:55:08 +01:00
Andras Bacsai
f7fca69a23 update sentry key 2023-12-13 11:53:50 +01:00
Andras Bacsai
f954ee15c3 fix: init script echos 2023-12-13 11:53:01 +01:00
Andras Bacsai
3c54e01d87 improve more 2023-12-13 11:35:53 +01:00
Andras Bacsai
00d708610d improve local dev + contribution guide 2023-12-13 11:12:53 +01:00
Andras Bacsai
f3b04c1ef9 refactor: custom labels 2023-12-13 09:23:27 +01:00
Andras Bacsai
6b751f965b Merge pull request #1539 from coollabsio/next
v4.0.0-beta.161
2023-12-12 16:47:56 +01:00
Andras Bacsai
d3c9894479 fix: labels 2023-12-12 16:45:46 +01:00
Andras Bacsai
f68aace445 fix: non-ascii chars in labels 2023-12-12 16:34:05 +01:00
Andras Bacsai
f042c70b3c fix: labelling 2023-12-12 15:48:51 +01:00
Andras Bacsai
2116d79aad Merge pull request #1538 from coollabsio/next
v4.0.0-beta.160
2023-12-12 14:32:24 +01:00
Andras Bacsai
4bc63e283c fix: service env variable ovewritten if it has a default value 2023-12-12 14:28:11 +01:00
Andras Bacsai
d5804f99c2 Merge pull request #1537 from coollabsio/next
v4.0.0-beta.159
2023-12-12 13:25:40 +01:00
Andras Bacsai
dfc353ce54 fix: ignore if dynamic config could not be set 2023-12-12 13:20:26 +01:00
Andras Bacsai
8f65ddb754 Merge pull request #1536 from coollabsio/next
v4.0.0-beta.158
2023-12-12 12:41:46 +01:00
Andras Bacsai
29e750f0b2 hmm 2023-12-12 12:31:29 +01:00
Andras Bacsai
b24661b876 fix 2023-12-12 12:13:14 +01:00
Andras Bacsai
bbbd605f32 fix: comma in traefik custom labels 2023-12-12 12:10:46 +01:00
Andras Bacsai
ff13cb4e26 fix: init 2023-12-12 12:01:53 +01:00
Andras Bacsai
5cb572b6a5 fix: run init command after production seeder 2023-12-12 11:54:10 +01:00
Andras Bacsai
d8b97e06cf wip: fix for comma in labels 2023-12-11 23:34:18 +01:00
Andras Bacsai
becb4df950 Merge pull request #1535 from coollabsio/next
fix: is autoupdate not null
2023-12-11 23:27:12 +01:00
Andras Bacsai
20dc2b47fe fix: is autoupdate not null 2023-12-11 23:26:49 +01:00
Andras Bacsai
67df166c20 Merge pull request #1534 from coollabsio/next
v4.0.0-beta.157
2023-12-11 23:20:05 +01:00
Andras Bacsai
87c3d0048c fix 2023-12-11 21:43:53 +01:00
Andras Bacsai
1c71ac78e2 fix 2023-12-11 21:35:09 +01:00
Andras Bacsai
41181cac12 asdasdasd time to sleep 2023-12-11 21:30:13 +01:00
Andras Bacsai
41b6df0e6e fix 2023-12-11 21:26:21 +01:00
Andras Bacsai
4f3f98be0a fix 2023-12-11 21:26:18 +01:00
Andras Bacsai
7a97a4b69c fixes 2023-12-11 21:23:33 +01:00
Andras Bacsai
6ae87466ca fix: only allow to modify in .env file if AUTOUPDATE is set 2023-12-11 21:19:45 +01:00
Andras Bacsai
5159d47159 fix install script 2023-12-11 21:07:40 +01:00
Andras Bacsai
0138d04080 Merge pull request #1525 from American-Cloud/main
fix: Install script
2023-12-11 20:57:35 +01:00
Andras Bacsai
c803768e5f set autoupdate 2023-12-11 20:55:58 +01:00
Andras Bacsai
60c8e0d625 feat: disable autoupdate 2023-12-11 20:40:05 +01:00
Andras Bacsai
dd99ad0af8 fix fox 2023-12-11 20:29:40 +01:00
Andras Bacsai
24a1f02af5 version++ 2023-12-11 20:27:49 +01:00
Andras Bacsai
601a1e128e fixes 2023-12-11 20:22:31 +01:00
Andras Bacsai
ccb9769e67 finally works? 2023-12-11 20:13:41 +01:00
Andras Bacsai
d79da996d3 fix 2023-12-11 20:01:54 +01:00
Andras Bacsai
4f800f5331 hmm, why 2023-12-11 19:46:46 +01:00
Andras Bacsai
a19a58338c debug on 2023-12-11 19:39:27 +01:00
Andras Bacsai
8a80dbd5d8 fix 2023-12-11 19:36:44 +01:00
Andras Bacsai
ec5cca7b3e feat: autoupdate env during seed 2023-12-11 19:34:23 +01:00
Andras Bacsai
ce721c1764 fix 2023-12-11 19:30:37 +01:00
Andras Bacsai
f4d7c4f942 update 2023-12-11 19:25:35 +01:00
Andras Bacsai
40716550ec fix 2023-12-11 19:16:17 +01:00
Andras Bacsai
f0ee26cd86 fix realtimePort 2023-12-11 19:11:29 +01:00
Andras Bacsai
423dfc6280 fix 2023-12-11 19:02:06 +01:00
Andras Bacsai
6d9a66ff1b fix: websocket 2023-12-11 18:48:00 +01:00
Andras Bacsai
17c8872130 fix: realtime connection?! 2023-12-11 18:06:29 +01:00
Andras Bacsai
3ffa2b6b8d fix: add ipv6 2023-12-11 16:34:36 +01:00
Andras Bacsai
35134f2327 fix: pusher host 2023-12-11 16:32:41 +01:00
Andras Bacsai
8ed4b540e1 Merge pull request #1533 from coollabsio/next
v4.0.0-.beta.156
2023-12-11 15:24:15 +01:00
Andras Bacsai
47202a7951 fix: db status check 2023-12-11 15:23:41 +01:00
Andras Bacsai
fe6e76ad0d fix 2023-12-11 15:09:36 +01:00
Andras Bacsai
e9920f05f5 fix: proxy logs 2023-12-11 15:08:40 +01:00
Andras Bacsai
57a39f12bb version++ 2023-12-11 14:56:11 +01:00
Andras Bacsai
ba85e3bc8b Merge pull request #1532 from coollabsio/next
v4.0.0-beta.155
2023-12-11 14:47:59 +01:00
Andras Bacsai
1fd12832ca refactor: application status changed realtime 2023-12-11 13:43:16 +01:00
Andras Bacsai
a8807b8d09 Merge pull request #1530 from stooit/feat/docker-compose-prebuild
feat: Build images prior to rollout in docker compose buildpack.
2023-12-11 13:03:59 +01:00
Andras Bacsai
dbc55233cb fix: add new destination 2023-12-11 13:02:29 +01:00
Andras Bacsai
af6d94c0d8 fix: realtime check 2023-12-11 12:40:56 +01:00
Andras Bacsai
e022492770 fix: realtime connection popup could be disabled 2023-12-11 12:03:32 +01:00
Andras Bacsai
5e03979f9c no cleanup 2023-12-11 11:29:59 +01:00
Andras Bacsai
956416b522 refactor: service logs are now on one page 2023-12-11 11:27:41 +01:00
Andras Bacsai
2846e049fa fix: ui 2023-12-11 10:29:03 +01:00
Andras Bacsai
3ffd3fc819 fix: channels
feat: database backup is realtime now
2023-12-11 10:23:10 +01:00
Andras Bacsai
63dff5961e fix 2023-12-11 09:41:31 +01:00
Andras Bacsai
4b024017ad fix: live mode for github webhooks 2023-12-11 09:36:21 +01:00
Andras Bacsai
720bb8c478 fix: database ui is realtime based 2023-12-11 09:02:53 +01:00
Andras Bacsai
771dc30b81 fix: do not send telegram noti on intent payment failed 2023-12-11 08:32:42 +01:00
Andras Bacsai
6f97af096d Merge pull request #1526 from stooit/feat/application-run-command
fix: Escape container commands
2023-12-11 08:23:29 +01:00
Andras Bacsai
3d28669cad fix: boarding view 2023-12-10 15:57:46 +01:00
Stuart Rowlands
efe043ec9d Build images prior to rollout. 2023-12-09 19:14:06 -08:00
Andras Bacsai
6bb79e10bc fix: double ws connection 2023-12-08 22:51:42 +01:00
Andras Bacsai
ba2e4c06f1 fix 2023-12-08 21:37:20 +01:00
Andras Bacsai
1bfb9637ba hmm 2023-12-08 21:12:22 +01:00
Stuart Rowlands
d7e9821582 Resolve merge conflicts. 2023-12-08 12:07:27 -08:00
Stuart Rowlands
08585f7e9a Merge branch 'next' into feat/application-run-command 2023-12-08 12:06:09 -08:00
Andras Bacsai
3eedb43b66 fix 2023-12-08 21:04:27 +01:00
Andras Bacsai
26aca6a4c0 remove polling 2023-12-08 20:13:17 +01:00
Andras Bacsai
3cbf8c281d remove polling 2023-12-08 20:12:54 +01:00
Andras Bacsai
d931d57373 fix github change 2023-12-08 19:57:55 +01:00
Andras Bacsai
4e680deb93 fix: service deletion job 2023-12-08 18:32:08 +01:00
Andras Bacsai
6a6275d4fa revert 2023-12-08 17:08:02 +01:00
Andras Bacsai
efd9087b74 fix buildpack form 2023-12-08 15:27:11 +01:00
Andras Bacsai
6882ce8d0f fix: service start + event 2023-12-08 15:12:08 +01:00
Andras Bacsai
eccd41217f fix network name 2023-12-08 15:07:50 +01:00
Andras Bacsai
538de9bd81 rename soketi container to realtime 2023-12-08 14:57:11 +01:00
Andras Bacsai
a249ee1b1f fix: live event 2023-12-08 13:55:55 +01:00
Andras Bacsai
828fec9448 fix: do not create duplicated networks 2023-12-08 13:24:28 +01:00
Andras Bacsai
205995cabe stop status event 2023-12-08 13:07:42 +01:00
Andras Bacsai
4071e096bc hmm 2023-12-08 12:48:21 +01:00
Andras Bacsai
14bac0f0d7 hmm 2023-12-08 12:40:32 +01:00
Andras Bacsai
69c124032c fix 2023-12-08 12:23:12 +01:00
Andras Bacsai
b55bd298f2 fix: service navbar using new realtime events 2023-12-08 12:12:44 +01:00
Andras Bacsai
86c2415210 ui fix 2023-12-08 09:15:32 +01:00
Andras Bacsai
82f3d54bc3 ui fixes 2023-12-08 00:16:22 +01:00
Andras Bacsai
a6e76dfabc custom progress bar colro 2023-12-07 23:31:03 +01:00
Jason Hollis
c1f6bf41f5 fix: Install script parse version
* Allow version to be passed with v or V at the beginning of
      version.  This allows users to pass along the actual github tagged
      version as it is listed on github.
    * Linting updates
2023-12-07 16:58:17 -05:00
Andras Bacsai
f934dfef33 wip livewire migration 2023-12-07 22:56:55 +01:00
Stuart Rowlands
19c66c6628 Escape command in ExecuteContainerCommand. 2023-12-07 11:05:52 -08:00
Stuart Rowlands
d7d948caf6 Merge branch 'main' into feat/application-run-command 2023-12-07 10:48:19 -08:00
Stuart Rowlands
1b34337fe8 Wrap command run to support quotes & chaining. 2023-12-07 10:45:11 -08:00
Andras Bacsai
718603e37e wip: migrate to livewire 3 2023-12-07 19:06:32 +01:00
Jason Hollis
9df0a2e545 fix: Better handling of errors with install script 2023-12-07 12:08:43 -05:00
Andras Bacsai
7ffebd71f3 Merge pull request #1518 from coollabsio/next
v4.0.0-beta.154
2023-12-07 14:04:03 +01:00
Andras Bacsai
2f286a6595 fix: container selection 2023-12-07 13:48:23 +01:00
Andras Bacsai
13701f6030 ui: env vars 2023-12-07 13:31:06 +01:00
Andras Bacsai
91f224ddea Merge pull request #1461 from DanHulton/feature/better_env_vars
Friendlier env vars page.
2023-12-07 13:29:41 +01:00
Andras Bacsai
8cffae10b2 rename things 2023-12-07 13:28:11 +01:00
Andras Bacsai
1158b2f4db feat: execute command in container 2023-12-07 13:07:16 +01:00
Andras Bacsai
f542bcf428 Merge pull request #1524 from stooit/feat/application-run-command
Add support for command execution in containers.
2023-12-07 11:29:32 +01:00
Stuart Rowlands
22178df8ae Add support for command execution in containers. 2023-12-06 15:42:14 -08:00
Andras Bacsai
acfe1daf9b fix: switching to static build 2023-12-06 21:32:23 +01:00
Andras Bacsai
fb3f71881f fix: use hc port 80 in case of static build 2023-12-06 21:09:41 +01:00
Andras Bacsai
0f7546a4dc add error message if realtime service is not reachable 2023-12-06 18:10:49 +01:00
Andras Bacsai
6ccafacc87 very important commit 2023-12-06 17:02:27 +01:00
Andras Bacsai
852c2df12e mono 2023-12-06 17:02:07 +01:00
Andras Bacsai
4c752951ab asd 2023-12-06 17:00:05 +01:00
Andras Bacsai
f32191b889 fix modal 2023-12-06 16:59:52 +01:00
Andras Bacsai
31251ef6cb nothing to see here 2023-12-06 16:38:46 +01:00
Andras Bacsai
e9365aa09b update js 2023-12-06 16:34:34 +01:00
Andras Bacsai
e5c860319f modal fix 2023-12-06 16:32:40 +01:00
Andras Bacsai
ceedd5225f modal fix? 2023-12-06 15:50:13 +01:00
Andras Bacsai
61efdfb7c1 revert 2023-12-06 15:30:04 +01:00
Andras Bacsai
fcef5d902e hmm 2023-12-06 15:04:07 +01:00
Andras Bacsai
2073b8949b lets see now 2023-12-06 14:57:03 +01:00
Andras Bacsai
5c59a752e3 test modal closing problem 2023-12-06 14:47:29 +01:00
Andras Bacsai
caf6e3a23e fix 2023-12-06 14:23:18 +01:00
Andras Bacsai
8eb1c4da46 fix prod compose 2023-12-06 14:22:19 +01:00
Andras Bacsai
bab2f391ed fix 2023-12-06 14:21:30 +01:00
Andras Bacsai
131c6df82a fix 2023-12-06 14:11:02 +01:00
Andras Bacsai
9f44d0c47a test 2023-12-06 13:50:27 +01:00
Andras Bacsai
dff7ed5b7b fix: bind volumes for compose bp 2023-12-06 13:45:43 +01:00
Andras Bacsai
54b6472f3b update packages 2023-12-06 13:32:20 +01:00
Andras Bacsai
04a36e5c90 update packages 2023-12-06 13:29:10 +01:00
Andras Bacsai
b2f851272b updates 2023-12-06 12:57:50 +01:00
Andras Bacsai
778915599f fix 2023-12-06 12:28:05 +01:00
Andras Bacsai
a6f9527157 fix 2023-12-06 12:20:20 +01:00
Andras Bacsai
db976709c7 add local js for pusher/echo 2023-12-06 10:32:49 +01:00
Andras Bacsai
7e4947ba07 setup test event 2023-12-06 10:25:23 +01:00
Andras Bacsai
e2578a7dd0 fix: deploy the right compose file 2023-12-06 09:36:11 +01:00
Andras Bacsai
7d028b15f5 wip 2023-12-05 15:26:05 +01:00
Andras Bacsai
d240bfda8b wip 2023-12-05 15:20:47 +01:00
Andras Bacsai
d59ec2548a wip 2023-12-05 15:19:54 +01:00
Andras Bacsai
e0cefc787a fix: add hc for soketi 2023-12-05 15:15:01 +01:00
Andras Bacsai
0496198d0f wip 2023-12-05 15:09:46 +01:00
Andras Bacsai
cbf7f1fa41 wip 2023-12-05 14:56:00 +01:00
Andras Bacsai
66587d864a wip 2023-12-05 14:52:19 +01:00
Andras Bacsai
b106705c8d wip 2023-12-05 14:18:44 +01:00
Andras Bacsai
5cc22a8271 wip: 🌮 2023-12-05 13:56:11 +01:00
Andras Bacsai
cd22863a8c wip 2023-12-05 12:31:07 +01:00
Andras Bacsai
118a02f70d wip 2023-12-05 12:18:23 +01:00
Andras Bacsai
862177d61a wip 2023-12-05 12:16:48 +01:00
Andras Bacsai
41280aa780 wip 2023-12-05 12:13:57 +01:00
Andras Bacsai
3a987c8a6e wip 2023-12-05 11:47:40 +01:00
Andras Bacsai
29ca461a9a wip 2023-12-05 11:35:05 +01:00
Andras Bacsai
d48b72c160 wip 2023-12-05 11:24:23 +01:00
Andras Bacsai
ce49f26c53 wip 2023-12-05 11:17:52 +01:00
Andras Bacsai
02989678be revert a few more things 2023-12-04 21:48:10 +01:00
Andras Bacsai
810b55163c revert: wip 2023-12-04 21:37:30 +01:00
Andras Bacsai
ac13ba0957 wip 2023-12-04 21:13:46 +01:00
Andras Bacsai
a3e088199f wip 2023-12-04 21:06:02 +01:00
Andras Bacsai
42ee4ca032 wip: broadcast 2023-12-04 20:47:32 +01:00
Andras Bacsai
17deff4d86 wip 2023-12-04 15:42:08 +01:00
Andras Bacsai
8b6323b906 refactor applicationdeploymentjob 2023-12-04 15:08:24 +01:00
Andras Bacsai
a696b5271a Update release version to 4.0.0-beta.154 2023-12-04 12:13:21 +01:00
Andras Bacsai
31959f26d4 Merge pull request #1517 from coollabsio/next
v4.0.0-beta.153
2023-12-04 11:42:21 +01:00
Andras Bacsai
45d2f80f69 fix: missing docker image thing 2023-12-04 11:41:44 +01:00
Dan Hulton
ccb972dcb9 Show row-based env var display at lg, not xl. Add border for col-based env var display. 2023-11-16 21:49:51 -05:00
760 changed files with 26546 additions and 9816 deletions

View File

@@ -4,3 +4,7 @@ APP_KEY=
DB_PASSWORD=
REDIS_PASSWORD=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,8 +22,6 @@ You can ask for guidance anytime on our
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
@@ -31,7 +29,6 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).

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.
@@ -17,6 +28,42 @@ https://coolify.io/sponsorships
Thank you so much!
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
<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 ($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://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>
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
## Organizations
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
# Cloud
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
@@ -32,16 +79,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
@@ -59,33 +96,9 @@ Contact us [here](https://coolify.io/docs/contact).
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
# 💰 Financial Contributors
# Repo Activity
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
## Organizations
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="appwrite logo" width="200"/></a>
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
Support this project with your organization. Your logo will show up here with a link to your website.
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image")
# Star History

View File

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

View File

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

View File

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

View File

@@ -17,24 +17,24 @@ class RunRemoteProcess
public bool $hide_from_output;
public bool $is_finished;
public bool $ignore_errors;
public $call_event_on_finish = null;
protected $time_start;
protected $current_time;
protected $last_write_at = 0;
protected $throttle_interval_ms = 500;
protected $throttle_interval_ms = 200;
protected int $counter = 1;
/**
* Create a new job instance.
*/
public function __construct(Activity $activity, bool $hide_from_output = false, bool $is_finished = false, bool $ignore_errors = false)
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
{
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
@@ -43,8 +43,8 @@ class RunRemoteProcess
$this->activity = $activity;
$this->hide_from_output = $hide_from_output;
$this->is_finished = $is_finished;
$this->ignore_errors = $ignore_errors;
$this->call_event_on_finish = $call_event_on_finish;
}
public static function decodeOutput(?Activity $activity = null): string
@@ -74,17 +74,29 @@ class RunRemoteProcess
$this->time_start = hrtime(true);
$status = ProcessStatus::IN_PROGRESS;
$processResult = Process::forever()->run($this->getCommand(), $this->handleOutput(...));
$timeout = config('constants.ssh.command_timeout');
$process = Process::timeout($timeout)->start($this->getCommand(), $this->handleOutput(...));
$this->activity->properties = $this->activity->properties->merge([
'process_id' => $process->id(),
]);
$processResult = $process->wait();
// $processResult = Process::timeout($timeout)->run($this->getCommand(), $this->handleOutput(...));
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
$status = ProcessStatus::ERROR;
} else {
if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
if ($processResult->exitCode() == 0) {
$status = ProcessStatus::FINISHED;
}
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
$status = ProcessStatus::ERROR;
}
// if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
// $status = ProcessStatus::FINISHED;
// }
// if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
// $status = ProcessStatus::ERROR;
// }
}
$this->activity->properties = $this->activity->properties->merge([
@@ -97,7 +109,15 @@ class RunRemoteProcess
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
}
if ($this->call_event_on_finish) {
try {
event(resolve("App\\Events\\$this->call_event_on_finish", [
'userId' => $this->activity->causer_id,
]));
} catch (\Throwable $e) {
ray($e);
}
}
return $processResult;
}
@@ -117,7 +137,6 @@ class RunRemoteProcess
}
$this->current_time = $this->elapsedTime();
$this->activity->description = $this->encodeOutput($type, $output);
if ($this->isAfterLastThrottle()) {
// Let's write to database.
DB::transaction(function () {

View File

@@ -56,8 +56,7 @@ class StartMariadb
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMariadb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -104,8 +106,8 @@ class StartMariadb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -138,7 +140,7 @@ class StartMariadb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -63,8 +63,7 @@ class StartMongodb
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -76,6 +75,9 @@ class StartMongodb
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -120,8 +122,8 @@ class StartMongodb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -154,7 +156,7 @@ class StartMongodb
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {

View File

@@ -56,8 +56,7 @@ class StartMysql
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -69,6 +68,9 @@ class StartMysql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -104,8 +106,8 @@ class StartMysql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -138,7 +140,7 @@ class StartMysql
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {

View File

@@ -50,12 +50,8 @@ class StartPostgresql
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'pg_isready',
'-d',
$this->database->postgres_db,
'-U',
$this->database->postgres_user,
"CMD-SHELL",
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
],
'interval' => '5s',
'timeout' => '5s',
@@ -66,8 +62,7 @@ class StartPostgresql
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -79,6 +74,9 @@ class StartPostgresql
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
ray('Log Drain Enabled');
$docker_compose['services'][$container_name]['logging'] = [
@@ -130,8 +128,8 @@ class StartPostgresql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -166,7 +164,7 @@ class StartPostgresql
ray('Generate Environment Variables')->green();
ray($this->database->runtime_environment_variables)->green();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {

View File

@@ -3,6 +3,7 @@
namespace App\Actions\Database;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -65,8 +66,7 @@ class StartRedis
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
@@ -78,6 +78,9 @@ class StartRedis
]
]
];
if (!is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
}
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
@@ -104,7 +107,7 @@ class StartRedis
'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
@@ -114,8 +117,8 @@ class StartRedis
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
private function generate_local_persistent_volumes()
@@ -148,7 +151,7 @@ class StartRedis
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
$environment_variables->push("$env->key=$env->real_value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
@@ -163,8 +166,9 @@ class StartRedis
return;
}
$filename = 'redis.conf';
$content = $this->database->redis_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
Storage::disk('local')->put("tmp/redis.conf_{$this->database->uuid}", $this->database->redis_conf);
$path = Storage::path("tmp/redis.conf_{$this->database->uuid}");
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
Storage::disk('local')->delete("tmp/redis.conf_{$this->database->uuid}");
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
@@ -16,6 +17,9 @@ class StopDatabase
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
{
$server = $database->destination->server;
if (!$server->isFunctional()) {
return 'Server is not functional';
}
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server

View File

@@ -21,7 +21,10 @@ class CheckProxy
$status = getContainerStatus($server, 'coolify-proxy_traefik');
$server->proxy->set('status', $status);
$server->save();
return false;
if ($status === 'running') {
return false;
}
return true;
} else {
$status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') {

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Proxy;
use App\Events\ProxyStatusChanged;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,8 +14,10 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity
{
try {
$proxyType = $server->proxyType();
if ($proxyType === 'NONE') {
return 'OK';
}
$commands = collect([]);
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
@@ -27,7 +30,7 @@ class StartProxy
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
@@ -35,7 +38,7 @@ class StartProxy
]);
} else {
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class CleanupDocker
{
use AsAction;
public function handle(Server $server, bool $force = true)
{
if ($force) {
instant_remote_process(['docker image prune -af'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -af'], $server, false);
} else {
instant_remote_process(['docker image prune -f'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false);
}
}
}

View File

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

View File

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

View File

@@ -18,13 +18,13 @@ class UpdateCoolify
try {
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0)->first();
$this->server = Server::find(0);
if (!$this->server) {
return;
}
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
if ($settings->next_channel) {
ray('next channel enabled');
$this->latestVersion = 'next';
@@ -43,7 +43,7 @@ class UpdateCoolify
}
$this->update();
}
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class DeleteService
{
use AsAction;
public function handle(Service $service)
{
try {
$server = data_get($service, 'server');
if ($server->isFunctional()) {
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
$commands = [];
foreach ($service->applications()->get() as $application) {
$storages = $application->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
}
foreach ($service->databases()->get() as $database) {
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
}
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
$commands[] = "docker rm -f $service->uuid";
instant_remote_process($commands, $server, false);
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
} finally {
foreach ($service->applications()->get() as $application) {
$application->forceDelete();
}
foreach ($service->databases()->get() as $database) {
$database->forceDelete();
}
foreach ($service->scheduled_tasks as $task) {
$task->delete();
}
$service->tags()->detach();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Actions\Shared;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class PullImage
{
use AsAction;
public function handle(Service $resource)
{
$resource->saveComposeConfigs();
$commands[] = "cd " . $resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = "docker compose pull";
$server = data_get($resource, 'server');
if (!$server) return;
instant_remote_process($commands, $resource->server);
}
}

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,59 @@
<?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()
{
echo "Running database cleanup...\n";
$keep_days = 60;
// 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));
$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));
$count = $application_deployment_queues->count();
echo "Delete $count entries from application_deployment_queues.\n";
if ($this->option('yes')) {
$application_deployment_queues->delete();
}
// Cleanup webhooks table
$webhooks = DB::table('webhooks')->where('created_at', '<', now()->subDays($keep_days));
$count = $webhooks->count();
echo "Delete $count entries from webhooks.\n";
if ($this->option('yes')) {
$webhooks->delete();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\InstanceSettings;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Process;
class Dev extends Command
{
protected $signature = 'dev:init';
protected $description = 'Init the app in dev mode';
public function handle()
{
// Generate APP_KEY if not exists
if (empty(env('APP_KEY'))) {
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (!$settings) {
echo "Initializing instance, seeding database.\n";
Artisan::call('migrate --seed');
} else {
echo "Instance already initialized.\n";
}
// Set permissions
Process::run(['chmod', '-R', 'o+rwx', '.']);
}
}

View File

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

View File

@@ -4,38 +4,78 @@ namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class Init extends Command
{
protected $signature = 'app:init {--cleanup}';
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $description = 'Cleanup instance related stuffs';
public function handle()
{
$this->alive();
$cleanup = $this->option('cleanup');
if ($cleanup) {
echo "Running cleanup\n";
$this->cleanup_stucked_resources();
// $this->cleanup_ssh();
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments();
return;
}
if ($full_cleanup) {
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
try {
setup_dynamic_configuration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
if (!is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
return;
}
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources');
}
private function restore_coolify_db_backup()
{
try {
$database = StandalonePostgresql::withTrashed()->find(0);
if ($database && $database->trashed()) {
echo "Restoring coolify db backup\n";
$database->restore();
$scheduledBackup = ScheduledDatabaseBackup::find(0);
if (!$scheduledBackup) {
ScheduledDatabaseBackup::create([
'id' => 0,
'enabled' => true,
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $database->id,
'database_type' => 'App\Models\StandalonePostgresql',
'team_id' => 0,
]);
}
}
} catch (\Throwable $e) {
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_helper_containers()
{
@@ -57,7 +97,7 @@ class Init extends Command
return;
}
try {
Http::get("https://get.coollabs.io/coolify/v4/alive?appId=$id&version=$version");
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
echo "I am alive!\n";
} catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n";
@@ -85,8 +125,13 @@ class Init extends Command
// Cleanup any failed deployments
try {
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
foreach ($halted_deployments as $deployment) {
if (isCloud()) {
return;
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
ray($deployment->id, $deployment->status);
echo "Cleaning up deployment: {$deployment->id}\n";
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
}
@@ -94,167 +139,4 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_resources()
{
// Cleanup any resources that are not attached to any environment or destination or server
try {
$applications = Application::all();
foreach ($applications as $application) {
if (!data_get($application, 'environment')) {
echo 'Application without environment' . $application->name . 'deleting\n';
$application->delete();
}
if (!$application->destination()) {
echo 'Application without destination' . $application->name . 'deleting\n';
$application->delete();
}
if (!data_get($application, 'destination.server')) {
echo 'Application without server' . $application->name . 'deleting\n';
$application->delete();
}
}
} catch (\Throwable $e) {
echo "Error in application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
$postgresql->delete();
}
if (!$postgresql->destination()) {
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
$postgresql->delete();
}
if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
$postgresql->delete();
}
}
} catch (\Throwable $e) {
echo "Error in postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::all();
foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) {
echo 'Redis without environment' . $redis->name . 'deleting\n';
$redis->delete();
}
if (!$redis->destination()) {
echo 'Redis without destination' . $redis->name . 'deleting\n';
$redis->delete();
}
if (!data_get($redis, 'destination.server')) {
echo 'Redis without server' . $redis->name . 'deleting\n';
$redis->delete();
}
}
} catch (\Throwable $e) {
echo "Error in redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
$mongodb->delete();
}
if (!$mongodb->destination()) {
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
$mongodb->delete();
}
if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
$mongodb->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment' . $mysql->name . 'deleting\n';
$mysql->delete();
}
if (!$mysql->destination()) {
echo 'Mysql without destination' . $mysql->name . 'deleting\n';
$mysql->delete();
}
if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server' . $mysql->name . 'deleting\n';
$mysql->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
$mariadb->delete();
}
if (!$mariadb->destination()) {
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
$mariadb->delete();
}
if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
$mariadb->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::all();
foreach ($services as $service) {
if (!data_get($service, 'environment')) {
echo 'Service without environment' . $service->name . 'deleting\n';
$service->delete();
}
if (!$service->destination()) {
echo 'Service without destination' . $service->name . 'deleting\n';
$service->delete();
}
if (!data_get($service, 'server')) {
echo 'Service without server' . $service->name . 'deleting\n';
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in service: {$e->getMessage()}\n";
}
try {
$serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceApplication without service' . $service->name . 'deleting\n';
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in serviceApplications: {$e->getMessage()}\n";
}
try {
$serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Jobs\DeleteResourceJob;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
@@ -91,7 +92,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
break;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}
@@ -115,7 +116,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
return;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}
@@ -139,7 +140,7 @@ class ServicesDelete extends Command
if (!$confirmed) {
return;
}
$toDelete->delete();
DeleteResourceJob::dispatch($toDelete);
}
}
}

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();
@@ -96,6 +108,8 @@ class ServicesGenerate extends Command
'slogan' => $slogan,
'compose' => $yaml,
'tags' => $tags,
'logo' => $logo,
'minversion' => $minversion,
];
if ($env_file) {
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));

View File

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

View File

@@ -5,12 +5,14 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Console\Scheduling\Schedule;
@@ -30,9 +32,12 @@ class Kernel extends ConsoleKernel
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
@@ -41,6 +46,11 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule);
$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)
@@ -56,16 +66,49 @@ class Kernel extends ConsoleKernel
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers;
$servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else {
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
}
}
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)
{
@@ -81,7 +124,6 @@ class Kernel extends ConsoleKernel
{
$scheduled_backups = ScheduledDatabaseBackup::all();
if ($scheduled_backups->isEmpty()) {
ray('no scheduled backups');
return;
}
foreach ($scheduled_backups as $scheduled_backup) {
@@ -103,6 +145,31 @@ class Kernel extends ConsoleKernel
}
}
private function check_scheduled_tasks($schedule)
{
$scheduled_tasks = ScheduledTask::all();
if ($scheduled_tasks->isEmpty()) {
return;
}
foreach ($scheduled_tasks as $scheduled_task) {
$service = $scheduled_task->service;
$application = $scheduled_task->application;
if (!$application && !$service) {
ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete();
continue;
}
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
}
$schedule->job(new ScheduledTaskJob(
task: $scheduled_task
))->cron($scheduled_task->frequency)->onOneServer();
}
}
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');

View File

@@ -16,9 +16,11 @@ class CoolifyTaskArgs extends Data
public string $command,
public string $type,
public ?string $type_uuid = null,
public ?int $process_id = null,
public ?Model $model = null,
public ?string $status = null ,
public bool $ignore_errors = false,
public $call_event_on_finish = null,
) {
if(is_null($status)){
$this->status = ProcessStatus::QUEUED->value;

View File

@@ -8,5 +8,7 @@ enum ProcessStatus: string
case IN_PROGRESS = 'in_progress';
case FINISHED = 'finished';
case ERROR = 'error';
case KILLED = 'killed';
case CANCELLED = 'cancelled';
case CLOSED = 'closed';
}

View File

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

View File

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

View File

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

View File

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

View File

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

28
app/Events/TestEvent.php Normal file
View File

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

View File

@@ -47,7 +47,7 @@ class Handler extends ExceptionHandler
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401);
}
return redirect()->guest($exception->redirectTo() ?? route('login'));
return redirect()->guest($exception->redirectTo() ?? route('login'));
}
/**
* Register the exception handling callbacks for the application.
@@ -77,6 +77,9 @@ class Handler extends ExceptionHandler
);
}
);
if (str($e->getMessage())->contains('No space left on device')) {
return;
}
ray('reporting to sentry');
Integration::captureUnhandledException($e);
});

View File

@@ -0,0 +1,161 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Visus\Cuid2\Cuid2;
class Deploy extends Controller
{
public function deploy(Request $request)
{
$teamId = get_team_id_from_token();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
} else if ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
{
$uuids = explode(',', $uuid);
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$message = collect([]);
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
if ($message->count() > 0) {
return response()->json(['message' => $message->toArray()], 200);
}
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
{
$tags = explode(',', $tags);
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
}
$message = collect([]);
foreach ($tags as $tag) {
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
if (!$found_tag) {
$message->push("Tag {$tag} not found.");
continue;
}
$applications = $found_tag->applications()->get();
$services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($applications as $resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
foreach ($services as $resource) {
$return_message = $this->deploy_resource($resource, $force);
$message = $message->merge($return_message);
}
}
if ($message->count() > 0) {
return response()->json(['message' => $message->toArray()], 200);
}
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): Collection
{
$message = collect([]);
if (gettype($resource) !== 'object') {
return $message->push("Resource ($resource) not found.");
}
$type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') {
queue_application_deployment(
application: $resource,
deployment_uuid: new Cuid2(7),
force_rebuild: $force,
);
$message->push("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.");
} 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.");
} 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.");
} 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.");
} 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.");
} else if ($type === 'App\Models\Service') {
StartService::run($resource);
$message->push("Service {$resource->name} started. It could take a while, be patient.");
}
return $message;
}
}

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 response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$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 response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$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 response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$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,54 @@
<?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 response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$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;
});
ray($servers);
return response()->json($servers);
}
public function server_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
if (is_null($server)) {
return response()->json(['error' => 'Server not found.'], 404);
}
$server->load(['settings']);
$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;
});
return response()->json($server);
}
}

View File

@@ -2,23 +2,68 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\StandalonePostgresql;
use App\Events\TestEvent;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Illuminate\Support\Facades\Password;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function realtime_test() {
if (auth()->user()?->currentTeam()->id !== 0) {
return redirect(RouteServiceProvider::HOME);
}
TestEvent::dispatch();
return 'Look at your other tab.';
}
public function verify() {
return view('auth.verify-email');
}
public function email_verify(EmailVerificationRequest $request) {
$request->fulfill();
$name = request()->user()?->name;
send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request) {
if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
'email' => Str::lower($arrayOfRequest['email']),
]);
$type = set_transanctional_email_settings();
if (!$type) {
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
$request->validate([Fortify::email() => 'required|email']);
$status = Password::broker(config('fortify.passwords'))->sendResetLink(
$request->only(Fortify::email())
);
if ($status == Password::RESET_LINK_SENT) {
return app(SuccessfulPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
if ($status == Password::RESET_THROTTLED) {
return response('Already requested a password reset in the past minutes.', 400);
}
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
public function link()
{
$token = request()->get('token');
@@ -39,7 +84,7 @@ class Controller extends BaseController
} else {
$team = $user->teams()->first();
}
if (is_null(data_get($user, 'email_verified_at'))){
if (is_null(data_get($user, 'email_verified_at'))) {
$user->email_verified_at = now();
$user->save();
}
@@ -51,98 +96,31 @@ class Controller extends BaseController
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function license()
{
if (!isCloud()) {
abort(404);
}
return view('settings.license', [
'settings' => InstanceSettings::get(),
]);
}
public function force_passoword_reset()
{
return view('auth.force-password-reset');
}
public function boarding()
{
if (currentTeam()->boarding || isDev()) {
return view('boarding');
} else {
return redirect()->route('dashboard');
}
}
public function settings()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database) {
$s3s = S3Storage::whereTeamId(0)->get();
}
return view('settings.configuration', [
'settings' => $settings,
'database' => $database,
's3s' => $s3s ?? [],
]);
} else {
return redirect()->route('dashboard');
}
}
public function team()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.index', [
'invitations' => $invitations,
]);
}
public function storages()
{
$s3 = S3Storage::ownedByCurrentTeam()->get();
return view('team.storages.all', [
's3' => $s3,
]);
}
public function storages_show()
{
$storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail();
return view('team.storages.show', [
'storage' => $storage,
]);
}
public function members()
{
$invitations = [];
if (auth()->user()->isAdminFromSession()) {
$invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
return view('team.members', [
'invitations' => $invitations,
]);
}
public function acceptInvitation()
public function accept_invitation()
{
try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();
if (auth()->user()->id !== $user->id) {
abort(401);
}
$invitationValid = $invitation->isValid();
if ($invitationValid) {
if ($resetPassword) {
$user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete();
return redirect()->route('team.index');
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
refreshSession($invitation->team);
$invitation->delete();
if (auth()->user()?->id !== $user->id) {
return redirect()->route('login');
}
refreshSession($invitation->team);
return redirect()->route('team.index');
} else {
abort(401);
@@ -153,7 +131,7 @@ class Controller extends BaseController
}
}
public function revokeInvitation()
public function revoke_invitation()
{
try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();

View File

@@ -1,84 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class DatabaseController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function configuration()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
return view('project.database.configuration', ['database' => $database]);
}
public function executions()
{
$backup_uuid = request()->route('backup_uuid');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
$backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first();
if (!$backup) {
return redirect()->route('dashboard');
}
$executions = collect($backup->executions)->sortByDesc('created_at');
return view('project.database.backups.executions', [
'database' => $database,
'backup' => $backup,
'executions' => $executions,
's3s' => currentTeam()->s3s,
]);
}
public function backups()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return redirect()->route('dashboard');
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
// No backups for redis
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
return view('project.database.backups.all', [
'database' => $database,
's3s' => currentTeam()->s3s,
]);
}
}

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

@@ -1,56 +0,0 @@
<?php
namespace App\Http\Livewire;
use App\Enums\ProcessStatus;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
class ActivityMonitor extends Component
{
public string|null $header = null;
public $activityId;
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newMonitorActivity'];
public function newMonitorActivity($activityId)
{
$this->activityId = $activityId;
$this->hydrateActivity();
$this->isPollingActive = true;
}
public function hydrateActivity()
{
$this->activity = Activity::query()
->find($this->activityId);
}
public function polling()
{
$this->hydrateActivity();
$this->setStatus(ProcessStatus::IN_PROGRESS);
$exit_code = data_get($this->activity, 'properties.exitCode');
if ($exit_code !== null) {
if ($exit_code === 0) {
$this->setStatus(ProcessStatus::FINISHED);
} else {
$this->setStatus(ProcessStatus::ERROR);
}
$this->isPollingActive = false;
$this->emit('activityFinished');
}
}
protected function setStatus($status)
{
$this->activity->properties = $this->activity->properties->merge([
'status' => $status,
]);
$this->activity->save();
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Http\Livewire;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
class Dashboard extends Component
{
public $projects = [];
public $servers = [];
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
}
// public function getIptables()
// {
// $servers = Server::ownedByCurrentTeam()->get();
// foreach ($servers as $server) {
// checkRequiredCommands($server);
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
// ray($iptables);
// }
// }
public function render()
{
return view('livewire.dashboard');
}
}

View File

@@ -1,27 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
class DeploymentLogs extends Component
{
public ApplicationDeploymentQueue $application_deployment_queue;
public $isKeepAliveOn = true;
protected $listeners = ['refreshQueue'];
public function refreshQueue()
{
$this->application_deployment_queue->refresh();
}
public function polling()
{
$this->emit('deploymentFinished');
$this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false;
}
}
}

View File

@@ -1,60 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Support\Collection;
use Livewire\Component;
class Deployments extends Component
{
public Application $application;
public Array|Collection $deployments = [];
public int $deployments_count = 0;
public string $current_url;
public int $skip = 0;
public int $default_take = 40;
public bool $show_next = false;
public ?string $pull_request_id = null;
protected $queryString = ['pull_request_id'];
public function mount()
{
$this->current_url = url()->current();
$this->show_pull_request_only();
$this->show_more();
}
private function show_pull_request_only() {
if ($this->pull_request_id) {
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
}
}
private function show_more()
{
if (count($this->deployments) !== 0) {
$this->show_next = true;
if (count($this->deployments) < $this->default_take) {
$this->show_next = false;
}
return;
}
}
public function reload_deployments()
{
$this->load_deployments();
}
public function load_deployments(int|null $take = null)
{
if ($take) {
$this->skip = $this->skip + $take;
}
$take = $this->default_take;
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
$this->deployments = $deployments;
$this->deployments_count = $count;
$this->show_pull_request_only();
$this->show_more();
}
}

View File

@@ -1,90 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerStatusJob;
use App\Models\Application;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Heading extends Component
{
public Application $application;
public array $parameters;
protected string $deploymentUuid;
public function mount()
{
$this->parameters = get_route_parameters();
}
public function check_status()
{
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));
}
}
public function force_deploy_without_cache()
{
$this->deploy(force_rebuild: true);
}
public function deploy(bool $force_rebuild = false)
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->emit('error', 'Please load a Compose file first.');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
protected function setDeploymentUuid()
{
$this->deploymentUuid = new Cuid2(7);
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}
public function stop()
{
StopApplication::run($this->application);
$this->application->status = 'exited';
$this->application->save();
$this->application->refresh();
}
public function restart()
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);
return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Project;
use Livewire\Component;
class Edit extends Component
{
public Project $project;
protected $rules = [
'project.name' => 'required|min:3|max:255',
'project.description' => 'nullable|string|max:255',
];
public function submit()
{
$this->validate();
try {
$this->project->save();
$this->emit('saved');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class ComposeModal extends Component
{
public ?string $raw = null;
public ?string $actual = null;
public function render()
{
return view('livewire.project.service.compose-modal');
}
public function submit() {
$this->emit('warning', "Saving new docker compose...");
$this->emit('saveCompose', $this->raw);
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
class Index extends Component
{
public Service $service;
public $applications;
public $databases;
public array $parameters;
public array $query;
protected $listeners = ["refreshStacks", "checkStatus"];
public function render()
{
return view('livewire.project.service.index');
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
}
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();
});
}
}

View File

@@ -1,41 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Models\Service;
use Livewire\Component;
class Navbar extends Component
{
public Service $service;
public array $parameters;
public array $query;
protected $listeners = ["checkStatus"];
public function render()
{
return view('livewire.project.service.navbar');
}
public function checkStatus() {
$this->service->refresh();
}
public function deploy()
{
$this->service->parse();
$activity = StartService::run($this->service);
$this->emit('newMonitorActivity', $activity->id);
}
public function stop(bool $forceCleanup = false)
{
StopService::run($this->service);
$this->service->refresh();
if ($forceCleanup) {
$this->emit('success', 'Force cleanup service successfully.');
} else {
$this->emit('success', 'Service stopped successfully.');
}
$this->emit('checkStatus');
}
}

View File

@@ -1,33 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Jobs\DeleteResourceJob;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Danger extends Component
{
public $resource;
public array $parameters;
public ?string $modalId = null;
public function mount()
{
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
}
public function delete()
{
try {
DeleteResourceJob::dispatchSync($this->resource);
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class Destination extends Component
{
public $resource;
public $servers = [];
public $additionalServers = [];
}

View File

@@ -1,71 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
public $parameters;
public ModelsEnvironmentVariable $env;
public ?string $modalId = null;
public bool $isDisabled = false;
public bool $isLocked = false;
public string $type;
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'Key',
'value' => 'Value',
'is_build_time' => 'Build Time',
'is_shown_once' => 'Shown Once',
];
public function mount()
{
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
$this->checkEnvs();
}
public function checkEnvs()
{
$this->isDisabled = false;
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
if ($this->env->is_shown_once) {
$this->isLocked = true;
}
}
public function lock()
{
$this->env->is_shown_once = true;
$this->env->save();
$this->checkEnvs();
$this->emit('refreshEnvs');
}
public function instantSave()
{
$this->submit();
}
public function submit()
{
$this->validate();
$this->env->save();
$this->emit('success', 'Environment variable updated successfully.');
$this->emit('refreshEnvs');
}
public function delete()
{
$this->env->delete();
$this->emit('refreshEnvs');
}
}

View File

@@ -1,88 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
class GetLogs extends Component
{
public string $outputs = '';
public string $errors = '';
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
public Server $server;
public ?string $container = null;
public ?bool $streamLogs = false;
public ?bool $showTimeStamps = true;
public int $numberOfLines = 100;
public function mount()
{
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$this->showTimeStamps = $this->resource->settings->is_include_timestamps;
} else {
if ($this->servicesubtype) {
$this->showTimeStamps = $this->servicesubtype->is_include_timestamps;
} else {
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
}
public function doSomethingWithThisChunkOfOutput($output)
{
$this->outputs .= removeAnsiColors($output);
}
public function instantSave()
{
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$this->resource->settings->is_include_timestamps = $this->showTimeStamps;
$this->resource->settings->save();
} else {
if ($this->servicesubtype) {
$this->servicesubtype->is_include_timestamps = $this->showTimeStamps;
$this->servicesubtype->save();
} else {
$this->resource->is_include_timestamps = $this->showTimeStamps;
$this->resource->save();
}
}
}
public function getLogs($refresh = false)
{
if ($this->container) {
if ($this->showTimeStamps) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
}
if ($refresh) {
$this->outputs = '';
}
Process::run($sshCommand, function (string $type, string $output) {
$this->doSomethingWithThisChunkOfOutput($output);
});
if ($this->showTimeStamps) {
$this->outputs = str($this->outputs)->split('/\n/')->sort(function ($a, $b) {
$a = explode(' ', $a);
$b = explode(' ', $b);
return $a[0] <=> $b[0];
})->join("\n");
}
}
}
public function render()
{
return view('livewire.project.shared.get-logs');
}
}

View File

@@ -1,83 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
class Logs extends Component
{
public ?string $type = null;
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
public Server $server;
public $container = [];
public $containers;
public $parameters;
public $query;
public $status;
public $serviceSubType;
public function mount()
{
$this->containers = collect();
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names']));
});
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
if (is_null($resource)) {
abort(404);
}
}
}
}
}
$this->resource = $resource;
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid;
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$service_name = data_get($this->parameters, 'service_name');
$this->serviceSubType = $this->resource->applications()->where('name', $service_name)->first();
if (!$this->serviceSubType) {
$this->serviceSubType = $this->resource->databases()->where('name', $service_name)->first();
}
$this->status = $this->resource->status;
$this->server = $this->resource->server;
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
}
}
public function render()
{
return view('livewire.project.shared.logs');
}
}

View File

@@ -1,49 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared\Storages;
use Livewire\Component;
class Add extends Component
{
public $uuid;
public $parameters;
public string $name;
public string $mount_path;
public string|null $host_path = null;
protected $listeners = ['clearAddStorage' => 'clear'];
protected $rules = [
'name' => 'required|string',
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
];
protected $validationAttributes = [
'name' => 'name',
'mount_path' => 'mount',
'host_path' => 'host',
];
public function mount()
{
$this->parameters = get_route_parameters();
}
public function submit()
{
$this->validate();
$name = $this->uuid . '-' . $this->name;
$this->emit('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);
}
public function clear()
{
$this->name = '';
$this->mount_path = '';
$this->host_path = null;
}
}

View File

@@ -1,153 +0,0 @@
<?php
namespace App\Http\Livewire\Settings;
use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Models\Server;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class Configuration extends Component
{
public ModelsInstanceSettings $settings;
public bool $do_not_track;
public bool $is_auto_update_enabled;
public bool $is_registration_enabled;
public bool $next_channel;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
protected $rules = [
'settings.fqdn' => 'nullable',
'settings.resale_license' => 'nullable',
'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required',
];
protected $validationAttributes = [
'settings.fqdn' => 'FQDN',
'settings.resale_license' => 'Resale License',
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
];
public function mount()
{
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
$this->next_channel = $this->settings->next_channel;
}
public function instantSave()
{
$this->settings->do_not_track = $this->do_not_track;
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
if ($this->next_channel) {
$this->settings->next_channel = false;
$this->next_channel = false;
} else {
$this->settings->next_channel = $this->next_channel;
}
$this->settings->save();
$this->emit('success', 'Settings updated!');
}
public function submit()
{
$this->resetErrorBag();
if ($this->settings->public_port_min > $this->settings->public_port_max) {
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
return;
}
$this->validate();
$this->settings->save();
$this->server = Server::findOrFail(0);
$this->setup_instance_fqdn();
$this->emit('success', 'Instance settings updated successfully!');
}
private function setup_instance_fqdn()
{
$file = "$this->dynamic_config_path/coolify.yaml";
if (empty($this->settings->fqdn)) {
instant_remote_process([
"rm -f $file",
], $this->server);
} else {
$url = Url::fromString($this->settings->fqdn);
$host = $url->getHost();
$schema = $url->getScheme();
$traefik_dynamic_conf = [
'http' =>
[
'routers' =>
[
'coolify-http' =>
[
'entryPoints' => [
0 => 'http',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
],
],
'services' =>
[
'coolify' =>
[
'loadBalancer' =>
[
'servers' =>
[
0 =>
[
'url' => 'http://coolify:80',
],
],
],
],
],
],
];
if ($schema === 'https') {
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
0 => 'redirect-to-https@docker',
];
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
'entryPoints' => [
0 => 'https',
],
'service' => 'coolify',
'rule' => "Host(`{$host}`)",
'tls' => [
'certresolver' => 'letsencrypt',
],
];
}
$this->save_configuration_to_disk($traefik_dynamic_conf, $file);
}
}
private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file)
{
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
$yaml =
"# This file is automatically generated by Coolify.\n" .
"# Do not edit it manually (only if you know what are you doing).\n\n" .
$yaml;
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $this->dynamic_config_path",
"echo '$base64' | base64 -d > $file",
], $this->server);
if (config('app.env') == 'local') {
ray($yaml);
}
}
}

View File

@@ -1,113 +0,0 @@
<?php
namespace App\Http\Livewire\Source\Github;
use App\Models\GithubApp;
use App\Models\InstanceSettings;
use Livewire\Component;
class Change extends Component
{
public string $webhook_endpoint;
public ?string $ipv4;
public ?string $ipv6;
public ?string $fqdn;
public ?bool $default_permissions = true;
public ?bool $preview_deployment_permissions = true;
public $parameters;
public GithubApp $github_app;
public string $name;
public bool $is_system_wide;
protected $rules = [
'github_app.name' => 'required|string',
'github_app.organization' => 'nullable|string',
'github_app.api_url' => 'required|string',
'github_app.html_url' => 'required|string',
'github_app.custom_user' => 'required|string',
'github_app.custom_port' => 'required|int',
'github_app.app_id' => 'required|int',
'github_app.installation_id' => 'required|int',
'github_app.client_id' => 'required|string',
'github_app.client_secret' => 'required|string',
'github_app.webhook_secret' => 'required|string',
'github_app.is_system_wide' => 'required|bool',
];
public function mount()
{
$github_app_uuid = request()->github_app_uuid;
$this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
if (!$this->github_app) {
return redirect()->route('source.all');
}
$settings = InstanceSettings::get();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->name = str($this->github_app->name)->kebab();
$this->fqdn = $settings->fqdn;
if ($settings->public_ipv4) {
$this->ipv4 = 'http://' . $settings->public_ipv4 . ':' . config('app.port');
}
if ($settings->public_ipv6) {
$this->ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port');
}
if ($this->github_app->installation_id && session('from')) {
$source_id = data_get(session('from'), 'source_id');
if (!$source_id || $this->github_app->id !== $source_id) {
session()->forget('from');
} else {
$parameters = data_get(session('from'), 'parameters');
$back = data_get(session('from'), 'back');
$environment_name = data_get($parameters, 'environment_name');
$project_uuid = data_get($parameters, 'project_uuid');
$type = data_get($parameters, 'type');
$destination = data_get($parameters, 'destination');
session()->forget('from');
return redirect()->route($back, [
'environment_name' => $environment_name,
'project_uuid' => $project_uuid,
'type' => $type,
'destination' => $destination,
]);
}
}
$this->parameters = get_route_parameters();
if (isCloud() && !isDev()) {
$this->webhook_endpoint = config('app.url');
} else {
$this->webhook_endpoint = $this->ipv4;
$this->is_system_wide = $this->github_app->is_system_wide;
}
}
public function submit()
{
try {
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->validate();
$this->github_app->save();
$this->emit('success', 'Github App updated successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
$this->submit();
}
public function delete()
{
try {
$this->github_app->delete();
redirect()->route('source.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class Sponsorship extends Component
{
public function disable()
{
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
}
public function render()
{
return view('livewire.sponsorship');
}
}

View File

@@ -1,29 +0,0 @@
<?php
namespace App\Http\Livewire\Team;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Delete extends Component
{
public function delete()
{
$currentTeam = currentTeam();
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
refreshSession();
return redirect()->route('team.index');
}
}

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Http\Livewire\Team;
use App\Models\Team;
use Livewire\Component;
class Form extends Component
{
public Team $team;
protected $rules = [
'team.name' => 'required|min:3|max:255',
'team.description' => 'nullable|min:3|max:255',
];
protected $validationAttributes = [
'team.name' => 'name',
'team.description' => 'description',
];
public function mount()
{
$this->team = currentTeam();
}
public function submit()
{
$this->validate();
try {
$this->team->save();
refreshSession();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -24,7 +24,7 @@ class CheckForcePasswordReset
}
$force_password_reset = auth()->user()->force_password_reset;
if ($force_password_reset) {
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
return $next($request);
}
return redirect()->route('auth.force-password-reset');

View File

@@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -11,9 +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('boarding');
return redirect()->route('boarding');
}
return $next($request);
}
@@ -21,27 +29,27 @@ class DecideWhatToDoWithUser
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
return $next($request);
}
return redirect('/verify');
return redirect()->route('verify.email');
}
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription');
return redirect()->route('subscription.index');
}
}
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding');
return redirect()->route('boarding');
}
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
return redirect('/');
return redirect(RouteServiceProvider::HOME);
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
return redirect('/');
if (isSubscriptionActive() && $request->routeIs('subscription.index')) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsBoardingFlow
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// ray()->showQueries()->color('orange');
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding');
}
return $next($request);
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsSubscriptionValid
{
public function handle(Request $request, Closure $next): Response
{
if (isInstanceAdmin()) {
return $next($request);
}
if (!auth()->user() || !isCloud()) {
if ($request->path() === 'subscription') {
return redirect('/');
} else {
return $next($request);
}
}
if (isSubscriptionActive() && $request->path() === 'subscription') {
// ray('active subscription Middleware');
return redirect('/');
}
if (isSubscriptionOnGracePeriod()) {
// ray('is_subscription_in_grace_period Middleware');
return $next($request);
}
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
// ray('SubscriptionValid Middleware');
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription');
} else {
return $next($request);
}
}
return $next($request);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -44,7 +44,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
{
// ray("checking log drain statuses for {$this->server->id}");
try {
if (!$this->server->isServerReady()) {
if (!$this->server->isFunctional()) {
return;
};
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
@@ -63,19 +63,19 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
Sleep::for(10)->seconds();
if ($this->healthcheck()) {
if ($this->server->log_drain_notification_sent) {
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]);
}
return;
}
if (!$this->server->log_drain_notification_sent) {
ray('Log drain container still unhealthy. Sending notification...');
$this->server->team->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
$this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
$this->server->update(['log_drain_notification_sent' => true]);
}
} else {
if ($this->server->log_drain_notification_sent) {
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]);
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
@@ -22,32 +23,48 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): int
{
return $this->server->id;
return $this->server->uuid;
}
public function __construct(public Server $server)
{
if (isDev()) $this->handle();
}
public function handle()
{
// ray("checking container statuses for {$this->server->id}");
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
if ($application->additional_servers->count() > 0) {
$skip_these_applications->push($application);
ComplexStatusCheck::run($application);
$applications = $applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id;
});
}
}
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id);
});
try {
if (!$this->server->isServerReady()) {
return;
};
if ($this->server->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else {
// Precheck for containers
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
@@ -55,15 +72,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicase = null;
$containerReplicates = null;
}
if (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicase) {
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
foreach ($containerReplicase as $containerReplica) {
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
@@ -82,7 +99,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
});
}
}
$applications = $this->server->applications();
$databases = $this->server->databases();
$services = $this->server->services()->get();
$previews = $this->server->previews();
@@ -159,6 +175,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;
}
}
$serviceLabelId = data_get($labels, 'coolify.serviceId');
if ($serviceLabelId) {
@@ -204,12 +223,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
$exitedServices = $exitedServices->unique('id');
foreach ($exitedServices as $exitedService) {
if ($exitedService->status === 'exited') {
if (str($exitedService->status)->startsWith('exited')) {
continue;
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
@@ -226,7 +245,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
foreach ($notRunningApplications as $applicationId) {
$application = $applications->where('id', $applicationId)->first();
if ($application->status === 'exited') {
if (str($application->status)->startsWith('exited')) {
continue;
}
$application->update(['status' => 'exited']);
@@ -251,7 +270,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
$preview = $previews->where('id', $previewId)->first();
if ($preview->status === 'exited') {
if (str($preview->status)->startsWith('exited')) {
continue;
}
$preview->update(['status' => 'exited']);
@@ -276,7 +295,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
$database = $databases->where('id', $database)->first();
if ($database->status === 'exited') {
if (str($database->status)->startsWith('exited')) {
continue;
}
$database->update(['status' => 'exited']);

View File

@@ -21,6 +21,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
public function __construct(
public Activity $activity,
public bool $ignore_errors = false,
public $call_event_on_finish = null
) {
}
@@ -32,6 +33,7 @@ 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
]);
$remote_process();

View File

@@ -3,6 +3,7 @@
namespace App\Jobs;
use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
@@ -50,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;
@@ -74,6 +78,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void
{
try {
BackupCreated::dispatch($this->team->id);
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
@@ -284,7 +289,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if ($this->backup->save_s3) {
$this->upload_to_s3();
}
$this->team->notify(new BackupSuccess($this->backup, $this->database));
$this->team?->notify(new BackupSuccess($this->backup, $this->database));
$this->backup_log->update([
'status' => 'success',
'message' => $this->backup_output,
@@ -300,19 +305,21 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]);
}
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e;
}
}
} catch (\Throwable $e) {
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
}
}
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\Application\StopApplication;
use App\Actions\Database\StopDatabase;
use App\Actions\Service\DeleteService;
use App\Actions\Service\StopService;
use App\Models\Application;
use App\Models\Service;
@@ -18,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
{
@@ -30,38 +32,29 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
public function handle()
{
try {
$server = $this->resource->destination->server;
if (!$server->isFunctional()) {
$this->resource->delete();
return 'Server is not functional';
}
$this->resource->forceDelete();
switch ($this->resource->type()) {
case 'application':
StopApplication::run($this->resource);
break;
case 'standalone-postgresql':
StopDatabase::run($this->resource);
break;
case 'standalone-redis':
StopDatabase::run($this->resource);
break;
case 'standalone-mongodb':
StopDatabase::run($this->resource);
break;
case 'standalone-mysql':
StopDatabase::run($this->resource);
break;
case 'standalone-mariadb':
StopDatabase::run($this->resource);
break;
case 'service':
StopService::run($this->resource);
DeleteService::run($this->resource);
break;
}
$this->resource->delete();
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
throw $e;
} finally {
Artisan::queue('cleanup:stucked-resources');
}
}
}

View File

@@ -2,7 +2,9 @@
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;
@@ -43,13 +45,12 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
ray('Usage before: ' . $this->usageBefore);
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name);
instant_remote_process(['docker image prune -af'], $this->server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server, false);
instant_remote_process(['docker builder prune -af'], $this->server, false);
CleanupDocker::run($this->server);
$usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
} else {
Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name);

View File

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

View File

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

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Jobs;
use App\Models\ScheduledTask;
use App\Models\ScheduledTaskExecution;
use App\Models\Server;
use App\Models\Application;
use App\Models\Service;
use App\Models\Team;
use Illuminate\Bus\Queueable;
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\Collection;
use Throwable;
class ScheduledTaskJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?Team $team = null;
public Server $server;
public ScheduledTask $task;
public Application|Service $resource;
public ?ScheduledTaskExecution $task_log = null;
public string $task_status = 'failed';
public ?string $task_output = null;
public array $containers = [];
public function __construct($task)
{
$this->task = $task;
if ($service = $task->service()->first()) {
$this->resource = $service;
} else if ($application = $task->application()->first()) {
$this->resource = $application;
} else {
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
}
$this->team = Team::find($task->team_id);
}
public function middleware(): array
{
return [new WithoutOverlapping($this->task->id)];
}
public function uniqueId(): int
{
return $this->task->id;
}
public function handle(): void
{
try {
$this->task_log = ScheduledTaskExecution::create([
'scheduled_task_id' => $this->task->id,
]);
$this->server = $this->resource->destination->server;
if ($this->resource->type() == 'application') {
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers[] = str_replace('/', '', $container['Names']);
});
}
}
elseif ($this->resource->type() == 'service') {
$this->resource->applications()->get()->each(function ($application) {
if (str(data_get($application, 'status'))->contains('running')) {
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
}
});
}
if (count($this->containers) == 0) {
throw new \Exception('ScheduledTaskJob failed: No containers running.');
}
if (count($this->containers) > 1 && empty($this->task->container)) {
throw new \Exception('ScheduledTaskJob failed: More than one container exists but no container name was provided.');
}
foreach ($this->containers as $containerName) {
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"';
$exec = "docker exec {$containerName} {$cmd}";
$this->task_output = instant_remote_process([$exec], $this->server, true);
$this->task_log->update([
'status' => 'success',
'message' => $this->task_output,
]);
return;
}
}
// No valid container was found.
throw new \Exception('ScheduledTaskJob failed: No valid container was found. Is the container name correct?');
} catch (\Throwable $e) {
if ($this->task_log) {
$this->task_log->update([
'status' => 'failed',
'message' => $this->task_output ?? $e->getMessage(),
]);
}
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
throw $e;
}
}
}

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,31 +16,38 @@ 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
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): int
{
return $this->server->id;
return $this->server->uuid;
}
public function handle(): void
public function handle()
{
ray("checking server status for {$this->server->id}");
if (!$this->server->isServerReady($this->tries)) {
throw new \RuntimeException('Server is not ready.');
};
try {
if ($this->server->isServerReady()) {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
}
} catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
handleError($e);
return handleError($e);
}
}
public function cleanup(bool $notify = false): void
@@ -54,7 +61,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
} else {
$this->server->high_disk_usage_notification_sent = true;
$this->server->save();
$this->server->team->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
}
} else {
DockerCleanupJob::dispatchSync($this->server);

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!');
}
}

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