Compare commits

...

294 Commits

Author SHA1 Message Date
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
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
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
Andras Bacsai
53975fcf61 Merge pull request #1516 from coollabsio/next
v4.0.0-beta.152
2023-12-04 11:26:17 +01:00
Andras Bacsai
76296c1f19 fix: prevent autorefresh of proxy status 2023-12-04 11:25:24 +01:00
Andras Bacsai
c25baf69e1 fix: workdir issue for basedir
fix: remove / mount on helpers image
2023-12-04 11:20:50 +01:00
Andras Bacsai
f952512615 fix: add cf tunnel to boarding server view 2023-12-04 09:29:55 +01:00
Andras Bacsai
c6557eada8 service: meilisearch 2023-12-03 12:16:33 +01:00
Andras Bacsai
2c2d74c0d6 Update release version to 4.0.0-beta.152 2023-12-01 22:16:48 +01:00
Andras Bacsai
028a2eb275 Fix Docker compose build command and remove debug statements 2023-12-01 22:16:27 +01:00
Andras Bacsai
ce7fad5bef Merge pull request #1511 from coollabsio/next
fix: use official install script with rancher (one will work for sure)
2023-12-01 14:02:30 +01:00
Andras Bacsai
cd7852e4f9 fix: use official install script with rancher (one will work for sure) 2023-12-01 14:02:11 +01:00
Andras Bacsai
7b022a2482 Merge pull request #1510 from coollabsio/next
v4.0.0-beta.151
2023-12-01 13:03:42 +01:00
Andras Bacsai
12d9b6538b Fix environment variable parsing in Docker Compose file 2023-12-01 12:34:23 +01:00
Andras Bacsai
335788c2d6 fix: default value do not overwrite existing env value 2023-12-01 12:14:23 +01:00
Andras Bacsai
2352e4a71d Fix directory creation inApplicationDeploymentJob.php 2023-12-01 12:13:55 +01:00
Andras Bacsai
dc03179bd1 feat: auto-restart tcp proxies for databases 2023-12-01 11:37:00 +01:00
Andras Bacsai
cc72f416e8 feat: custom log drain endpoints 2023-12-01 11:13:58 +01:00
Andras Bacsai
3b67d0a8de feat: save timestamp configuration for logs 2023-12-01 10:34:30 +01:00
Andras Bacsai
0135ba7e89 Delete docker-compose.prod.standalone.yml 2023-11-30 13:38:52 +01:00
Andras Bacsai
a28a28cd23 Add docker-compose.prod.standalone.yml
configuration file
2023-11-30 13:17:43 +01:00
Andras Bacsai
b52680a2d8 Fix dispatch_sync issue in ContainerStatusJob 2023-11-30 12:55:31 +01:00
Andras Bacsai
0670e6c1d6 fix: server view for link() 2023-11-30 12:21:53 +01:00
Andras Bacsai
c3882b75c1 Update release version to 4.0.0-beta.151 2023-11-30 12:21:42 +01:00
Andras Bacsai
64b6f86a36 Update PostgreSQL image version to 16-alpine for services 2023-11-30 12:21:21 +01:00
Andras Bacsai
b9efc22253 Merge pull request #1504 from coollabsio/next
v4.0.0-beta.150 - quick fix
2023-11-29 18:44:25 +01:00
Andras Bacsai
e3d9eb0154 Add hidden flag to docker compose command 2023-11-29 18:43:02 +01:00
Andras Bacsai
66f3967479 Merge pull request #1503 from coollabsio/next
v4.0.0-beta.150
2023-11-29 18:41:41 +01:00
Andras Bacsai
c54439e84c fix: dockercompose save ./ volumes under /data/coolify 2023-11-29 18:40:41 +01:00
Andras Bacsai
db4a4c74fc Merge pull request #1502 from coollabsio/next
v4.0.0-beta.149
2023-11-29 17:05:28 +01:00
Andras Bacsai
5c7ef80219 Fix container retrieval in CheckLogDrainContainerJob and ContainerStatusJob 2023-11-29 17:03:04 +01:00
Andras Bacsai
243d1c06fc cloud: disable trial 2023-11-29 16:34:31 +01:00
Andras Bacsai
ef25f7d800 Update Sentry DSN 2023-11-29 16:21:03 +01:00
Andras Bacsai
45640ffdb1 Update version numbers to 4.0.0-beta.149 2023-11-29 16:19:40 +01:00
Andras Bacsai
378291b209 Merge pull request #1501 from coollabsio/next
Commented out cleanup_ssh() function
2023-11-29 15:23:21 +01:00
Andras Bacsai
3583e552f1 Commented out cleanup_ssh() function 2023-11-29 15:23:03 +01:00
Andras Bacsai
d7dfeaf988 Merge pull request #1496 from coollabsio/next
v4.0.0-beta.148
2023-11-29 15:17:39 +01:00
Andras Bacsai
7fe5eca661 Add precheck for containers 2023-11-29 15:13:03 +01:00
Andras Bacsai
0dff57e69f Add cleanup option to app:init command 2023-11-29 15:03:21 +01:00
Andras Bacsai
f4803ad58b wip: swarm
fix: gitcompose deployments
2023-11-29 14:59:06 +01:00
Andras Bacsai
2d7bbbe300 wip: swarm 2023-11-29 10:06:52 +01:00
Andras Bacsai
928b68043b wip: swarm 2023-11-28 20:49:38 +01:00
Andras Bacsai
b21add0210 Update Swarm cluster label to Swarm Manager 2023-11-28 20:09:00 +01:00
Andras Bacsai
c41ffd6bfb wip: swarm 2023-11-28 18:42:09 +01:00
Andras Bacsai
b4874c7df3 wip: swarm 2023-11-28 18:31:04 +01:00
Andras Bacsai
c505a6ce9c wip 2023-11-28 15:49:24 +01:00
Andras Bacsai
706e4b13ee fix: sentry issue 2023-11-28 14:27:38 +01:00
Andras Bacsai
4af471ee31 fix: no container servers 2023-11-28 14:26:35 +01:00
Andras Bacsai
87062e4e22 Refactor application deployment job 2023-11-28 14:23:59 +01:00
Andras Bacsai
500ba0fab8 fix: do not remove deployment in case compose based failed 2023-11-28 14:08:42 +01:00
Andras Bacsai
1c72c127d5 Remove unused imports and fix import statement 2023-11-28 14:05:55 +01:00
Andras Bacsai
69bb4ae5ee Update release version to 4.0.0-beta.148 2023-11-28 13:40:33 +01:00
Andras Bacsai
5f8b8bd730 Merge pull request #1480 from coollabsio/next
v4.0.0-beta.147
2023-11-28 13:39:34 +01:00
Andras Bacsai
44f6d93639 Update installation script to include curl and wget 2023-11-28 13:28:15 +01:00
Andras Bacsai
e35b8a0f96 Add Stringable interface to validateOS method 2023-11-28 13:21:32 +01:00
Andras Bacsai
b26e23e7c3 Fix validateOS() return type 2023-11-28 13:17:59 +01:00
Andras Bacsai
e6f7e32037 Add SUPPORTED_OS constant based on /etc/os-release 2023-11-28 13:12:42 +01:00
Andras Bacsai
1c386db41d Update Docker installation command and add support for SLES 2023-11-28 13:12:25 +01:00
Andras Bacsai
085b655d9f Update version to 1.1.0 and add support for Redhat and Sles based operating systems 2023-11-28 13:02:12 +01:00
Andras Bacsai
2788fcf4e1 Add Docker Compose based applications and preview deployments to proxy on restart 2023-11-28 12:48:55 +01:00
Andras Bacsai
d058e04213 Add fqdn attribute to InstanceSettings model 2023-11-28 12:11:03 +01:00
Andras Bacsai
066f171163 Add Docker Compose file for Formbricks service 2023-11-28 12:05:14 +01:00
Andras Bacsai
2001be07d0 refactor: env variable generator 2023-11-28 12:05:04 +01:00
Andras Bacsai
39552cc42f fix: double default password length 2023-11-28 12:04:21 +01:00
Andras Bacsai
7f5d7e0eb0 Refactor application submit method to handle dockercompose build pack 2023-11-28 11:10:48 +01:00
Andras Bacsai
0eda49b104 fix: pull request build variables 2023-11-28 11:10:42 +01:00
Andras Bacsai
636995d0e4 Refactor server delete view 2023-11-28 10:55:24 +01:00
Andras Bacsai
4c0623f022 Refactor server delete view to display defined resources as links 2023-11-28 10:54:46 +01:00
Andras Bacsai
3e2e1080f5 nothing to see here 2023-11-28 10:46:00 +01:00
Andras Bacsai
3f866a07d8 Fix docker compose PR location default value 2023-11-28 10:11:53 +01:00
Andras Bacsai
23571ae104 wip 2023-11-27 15:50:22 +01:00
Andras Bacsai
c1710c8f7b moar fixes 2023-11-27 15:25:15 +01:00
Andras Bacsai
d4d2cc71a0 fix: lots of regarding git + docker compose deployments 2023-11-27 14:28:21 +01:00
Andras Bacsai
8d86d53292 fix: new logging for deployment jobs
fix: git based docker compose files
2023-11-27 11:54:55 +01:00
Andras Bacsai
fae97e4dee Fix network connection issues in Server and Service models 2023-11-27 09:58:31 +01:00
Andras Bacsai
8d0c3abf2e Refactor server delete view to display defined
resources
2023-11-27 09:42:23 +01:00
Andras Bacsai
d396f649df fix: show defined resources in server tab, so you will know what you need to delete before you can delete the server. 2023-11-27 09:39:43 +01:00
Andras Bacsai
ec21155c9e Update rules for field validation in StackForm.php 2023-11-24 21:38:39 +01:00
Andras Bacsai
58111f53b9 test wire:ignore 2023-11-24 21:35:01 +01:00
Andras Bacsai
2cbe1e8489 Add SMTP mail transport option to Ghost compose
file
2023-11-24 21:23:48 +01:00
Andras Bacsai
10e5a58b9e Add extra fields for MinIO, Weblate, and Ghost services 2023-11-24 21:04:15 +01:00
Andras Bacsai
6f886e8b6f Update Ghost configuration with mail options 2023-11-24 21:03:59 +01:00
Andras Bacsai
f96a91eb31 wip: compose based apps 2023-11-24 15:48:23 +01:00
Andras Bacsai
65a1961722 Add environment variables for Horizon balance 2023-11-24 10:12:37 +01:00
Andras Bacsai
c5a932ab88 Add environment variables for GitHub
authentication and email configuration
2023-11-24 08:38:49 +01:00
Andras Bacsai
d1e10dacc0 wip 2023-11-23 21:02:30 +01:00
Andras Bacsai
96327af838 Update log-drains.blade.php and add
trigger-with-external-database.yaml and
service-templates.json
2023-11-23 12:44:08 +01:00
Andras Bacsai
1cb6d594d0 Fix service loading issue in project select page 2023-11-23 11:49:49 +01:00
Andras Bacsai
16261fc36e Remove unnecessary code and update services list
loading
2023-11-23 11:40:29 +01:00
Andras Bacsai
cff694b0c4 Update Weblate configuration 2023-11-23 11:35:19 +01:00
Andras Bacsai
97fd56b9e4 Update number of servers in pricing plans 2023-11-23 10:57:11 +01:00
Andras Bacsai
72cfa3e7b0 Update server limits using environment variables 2023-11-23 10:51:57 +01:00
Andras Bacsai
3cf41e1e23 Update server basic value 2023-11-23 10:47:25 +01:00
Andras Bacsai
2a7a63a672 Add trigger.dev service 2023-11-23 09:05:22 +01:00
Andras Bacsai
7fb9e672cf Fix server execution method parameter name 2023-11-22 20:56:25 +01:00
Andras Bacsai
9012f6b953 Fix GitHub App retrieval in webhooks.php 2023-11-22 16:40:49 +01:00
Andras Bacsai
407eba8b76 Fix DockerCleanupJob exception message 2023-11-22 16:39:16 +01:00
Andras Bacsai
68f6ab5796 wip 2023-11-22 15:18:49 +01:00
Andras Bacsai
3dd36a2271 Fix container status handling and notifications 2023-11-22 15:18:37 +01:00
Andras Bacsai
7f69eb3c2e Merge pull request #1479 from coollabsio/next
v4.0.0-beta.146 - quick fix before release
2023-11-22 14:27:04 +01:00
Andras Bacsai
6ccbf911b2 Fix condition for pushing to Docker registry 2023-11-22 14:25:55 +01:00
Andras Bacsai
5c77cec68f Merge pull request #1478 from coollabsio/next
v4.0.0-beta.146
2023-11-22 14:22:10 +01:00
Andras Bacsai
25a0489f7f Fix log drain issue in advanced and service application 2023-11-22 14:21:03 +01:00
Andras Bacsai
5e27b88bef Add new console commands for root email change, root password reset, and service deletion 2023-11-22 13:21:25 +01:00
Andras Bacsai
ec98afe707 Merge pull request #1474 from coollabsio/next
v4.0.0-beta.145
2023-11-22 08:45:00 +01:00
Andras Bacsai
ce26127705 wip: new deployment jobs 2023-11-21 22:17:35 +01:00
Andras Bacsai
ef7fc1b260 Refactor code and update destination component 2023-11-21 15:31:46 +01:00
Andras Bacsai
f58e6766e1 Update Docker Engine version check 2023-11-21 13:06:05 +01:00
Andras Bacsai
4a21102983 fix: server adding process 2023-11-21 12:07:06 +01:00
Andras Bacsai
e78b6758d8 feat: add docker engine support install script to rhel based systems 2023-11-21 11:39:19 +01:00
Andras Bacsai
16eb7f4fb4 Add tracing option to Sentry configuration 2023-11-21 09:01:52 +01:00
Andras Bacsai
4974ce6eda Update release version to 4.0.0-beta.145 2023-11-21 08:41:43 +01:00
Andras Bacsai
6cdba17aca Update token retrieval in reset-password.blade.php 2023-11-20 15:16:23 +01:00
Andras Bacsai
30f8e8f232 fix: handle different label formats in services 2023-11-20 15:01:35 +01:00
Andras Bacsai
608f0b7840 Refactor Docker image name generation and push to
registry
2023-11-20 14:23:11 +01:00
Andras Bacsai
d0366c4054 Update Docker Registry link in general.blade.php 2023-11-20 13:58:31 +01:00
Andras Bacsai
f88e3c5b29 feat: push locally built image to docker registry
ui: fixes here and there
2023-11-20 13:49:10 +01:00
Andras Bacsai
e33fec0e1a Refactor checkbox component and update GPU
settings helper links
2023-11-20 11:37:09 +01:00
Andras Bacsai
912b0a263e feat: gpu enabled containers
feat: move advanced settings to different view
2023-11-20 11:35:31 +01:00
Andras Bacsai
8f963adbd4 fix: only report nonruntime errors 2023-11-20 10:32:06 +01:00
Andras Bacsai
8f2c24d7e9 fix: reset password 2023-11-18 17:50:44 +01:00
Andras Bacsai
9f3dbc3cbb Merge pull request #1469 from coollabsio/next
v4.0.0-beta.144
2023-11-17 21:28:18 +01:00
Andras Bacsai
8a9ee84925 Fix log drain container notification bug 2023-11-17 21:24:22 +01:00
Andras Bacsai
689480003a feat: log drainer container check 2023-11-17 21:16:25 +01:00
Andras Bacsai
3b20eee909 feat: enable/disable log drain by service 2023-11-17 20:08:21 +01:00
Andras Bacsai
e8cadc176b Merge pull request #1468 from coollabsio/next
v4.0.0-beta.143
2023-11-17 15:21:29 +01:00
Andras Bacsai
b0c96e64c9 Fix server unreachable notification count 2023-11-17 15:18:08 +01:00
Andras Bacsai
9ce3b43e09 Add Team model and merge servers with own servers 2023-11-17 15:11:29 +01:00
Andras Bacsai
4c2b3df861 Update server runtime and comments 2023-11-17 14:56:39 +01:00
Andras Bacsai
467471f54a Fix server readiness check in ContainerStatusJob and ServerStatusJob 2023-11-17 14:46:04 +01:00
Andras Bacsai
60171093c5 Update version to 4.0.0-beta.143 2023-11-17 14:43:57 +01:00
Andras Bacsai
38f2a2dac7 Merge pull request #1467 from coollabsio/next
v4.0.0-beta.142
2023-11-17 14:32:32 +01:00
Andras Bacsai
307ee52ac0 wtf 2023-11-17 14:29:37 +01:00
Andras Bacsai
b66c9835b7 Fix server status check and add new job 2023-11-17 14:22:05 +01:00
Andras Bacsai
d38d50dca2 Fix server readiness check and update version
number
2023-11-17 14:14:13 +01:00
Andras Bacsai
40023be4ea Merge pull request #1466 from coollabsio/next
Quick fix version
2023-11-17 14:02:00 +01:00
Andras Bacsai
48d7c6e76f Fix config version key 2023-11-17 13:59:45 +01:00
Andras Bacsai
debacfe2f7 Merge pull request #1465 from coollabsio/next
v4.0.0-beta.141
2023-11-17 13:54:20 +01:00
Andras Bacsai
d430813230 Update versions and add server readiness check 2023-11-17 13:53:56 +01:00
Andras Bacsai
e30c37b041 Merge pull request #1464 from coollabsio/next
v4.0.0-beta.140
2023-11-17 13:25:03 +01:00
Andras Bacsai
8c73068cc7 Refactor server filtering logic in Kernel.php 2023-11-17 13:11:46 +01:00
Andras Bacsai
2c4e69ad50 Fix server readiness check in ContainerStatusJob
and ServerStatusJob
2023-11-17 13:04:51 +01:00
Andras Bacsai
5ae08d009e Add skipServer() method to Server model 2023-11-17 12:47:15 +01:00
Andras Bacsai
673b944647 Fix IP address validation in server forms 2023-11-17 12:38:47 +01:00
Andras Bacsai
16281248ac Refactor Dockerfile deployment logic and server
validation
2023-11-17 12:22:45 +01:00
Andras Bacsai
8670b41671 fix: do not allow to enter local ip addresses 2023-11-17 11:56:14 +01:00
Andras Bacsai
9c69044da5 Merge pull request #1463 from coollabsio/next
v4.0.0-beta.139
2023-11-17 11:35:48 +01:00
Andras Bacsai
ebc4ab9af5 Remove unnecessary ray() statement 2023-11-17 11:33:46 +01:00
Andras Bacsai
57738198ad Add fluentd logging configuration for database & services 2023-11-17 11:32:52 +01:00
Andras Bacsai
b8252b85b0 Refactor logging configuration in ApplicationDeploymentJob.php 2023-11-17 11:13:16 +01:00
Andras Bacsai
479c2743bd Update Fluent Bit configuration file 2023-11-17 10:50:02 +01:00
Andras Bacsai
81e6482d7a Remove commented out code and fix indentation 2023-11-17 10:21:42 +01:00
Andras Bacsai
88c5d87084 Add log drain settings for New Relic,Highlight.io, and Axiom 2023-11-17 10:21:19 +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
Andras Bacsai
6c7e091e1b feat: log drain (wip) 2023-11-17 00:37:09 +01:00
Andras Bacsai
91e3d33c0b Add cleanup of stucked helper containers on servers 2023-11-16 20:48:25 +01:00
Andras Bacsai
aa00389824 Remove redundant sentence about cloud version in
README.md
2023-11-16 17:43:20 +01:00
Andras Bacsai
b4e54ab3e3 Improve Cloud version features and reduce
maintenance
2023-11-16 17:42:25 +01:00
Andras Bacsai
8f3c5d4bd3 Add donation link and update version numbers 2023-11-16 17:40:49 +01:00
Andras Bacsai
26668c71a1 Merge pull request #1460 from coollabsio/next
v4.0.0-beta.138
2023-11-16 15:28:48 +01:00
Andras Bacsai
bd7637c696 Add healthcheck URL to deployment job and update
version to beta.138
2023-11-16 15:23:07 +01:00
Andras Bacsai
cff54f48a3 Merge pull request #1459 from coollabsio/next
v4.0.0-beta.137
2023-11-16 14:39:48 +01:00
Andras Bacsai
5c0f239f62 Update server readiness check runtime to 1 minute 2023-11-16 14:36:43 +01:00
Andras Bacsai
d56c28c8d9 Remove unused notifications from
ContainerStatusJob
2023-11-16 14:29:23 +01:00
Andras Bacsai
2b666ff121 Refactor server and docker cleanup jobs 2023-11-16 14:29:01 +01:00
Andras Bacsai
fb42c43953 Add isLocalhost method to Server model and
conditionally show Cloudflare Tunnel checkbox in
server form view
2023-11-16 14:28:26 +01:00
Andras Bacsai
81437e6822 Fix high disk usage notification bug in
ServerStatusJob.php and HighDiskUsage.php
2023-11-16 13:49:08 +01:00
Andras Bacsai
2fe429fe92 Comment out logging configuration in
ApplicationDeploymentJob.php
2023-11-16 13:32:07 +01:00
Andras Bacsai
4f0b214042 Add timeout to ApplicationDeploymentJob 2023-11-16 13:27:51 +01:00
Andras Bacsai
c866213f34 fix: when to pull image 2023-11-16 13:22:12 +01:00
Andras Bacsai
7cec6330cf Update server status check and notifications 2023-11-16 11:53:37 +01:00
Andras Bacsai
f5de21a343 Add OTLP exporter and host metrics receiver
configuration to config.yaml.
2023-11-16 11:16:41 +01:00
Andras Bacsai
ecbfc4d790 Add Fluent Bit and New Relic configurations 2023-11-15 15:45:37 +01:00
Andras Bacsai
55ff00e028 Add logging configuration to compose file 2023-11-15 15:19:31 +01:00
Andras Bacsai
a0fc2bbb85 Merge pull request #1457 from coollabsio/next
v4.0.0-beta.136
2023-11-15 10:55:39 +01:00
Andras Bacsai
51a704b22a Remove middleware and uniqueId methods from
DockerCleanupJob
2023-11-15 10:37:55 +01:00
Andras Bacsai
6d49678842 Remove unnecessary echo and add alive message 2023-11-15 10:37:02 +01:00
Andras Bacsai
0459b3a115 Add init-script to prod-ssu Docker container 2023-11-15 10:31:48 +01:00
Andras Bacsai
82592c8222 Add alive request to Init command 2023-11-15 10:26:31 +01:00
Andras Bacsai
25bf8895e2 Add InstanceSettings to Init command 2023-11-15 10:20:48 +01:00
Andras Bacsai
f4f7bdf7d5 Update dependencies and add new feature 2023-11-15 10:18:41 +01:00
Andras Bacsai
c008564aa3 Merge pull request #1456 from coollabsio/next
v4.0.0-beta.135
2023-11-15 09:40:33 +01:00
Andras Bacsai
b825d98b2d Refactor storage connection handling and project
initialization
2023-11-15 09:34:27 +01:00
Andras Bacsai
1f711d9281 Update version and fix webhook generation 2023-11-15 09:15:49 +01:00
Andras Bacsai
1de850f640 Merge pull request #1453 from coollabsio/next
v4.0.0-beta.134
2023-11-14 19:31:24 +01:00
Andras Bacsai
f176247b02 Update application deployment and version numbers 2023-11-14 19:29:59 +01:00
422 changed files with 10297 additions and 4051 deletions

View File

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

View File

@@ -10,35 +10,40 @@ 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). For more information, take a look at our landing page [here](https://coolify.io).
> If you are looking for previous (v3) version, it is [here](https://github.com/coollabsio/coolify/tree/v3). # 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.
https://coolify.io/sponsorships
Thank you so much!
# Cloud # Cloud
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
You can easily attach your own servers, get all the automations, free email notifications, etc.
For more information & pricing, take a look at our landing page [here](https://coolify.io). For more information & pricing, take a look at our landing page [here](https://coolify.io).
# Beta ## Why should I use the Cloud version?
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
The latest version (v4) is still in beta. That does not mean it is unstable. All the features that are available are stable enough be usable in real-life. By subscribing to the cloud version, you get the Coolify server for the same price, but with:
- High-availability
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses. - Free email notifications
- Better support
- Less maintenance for you
# Installation # Installation
```bash ```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
``` ```
You can find the installation script source [here](./scripts/install.sh).
You can find the installation script [here](./scripts/install.sh). # Support
## Support
Contact us [here](https://coolify.io/docs/contact). Contact us [here](https://coolify.io/docs/contact).
## Recognitions # Recognitions
<p> <p>
<a href="https://news.ycombinator.com/item?id=26624341"> <a href="https://news.ycombinator.com/item?id=26624341">
@@ -54,11 +59,11 @@ 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> <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 # 💰 Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)] Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
### Organizations ## Organizations
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)! Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
@@ -78,10 +83,10 @@ Support this project with your organization. Your logo will show up here with 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/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> <a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
### Individuals ## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a> <a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
## Star History # Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date) [![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

View File

@@ -11,19 +11,34 @@ class StopApplication
public function handle(Application $application) public function handle(Application $application)
{ {
$server = $application->destination->server; $server = $application->destination->server;
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0); if ($server->isSwarm()) {
if ($containers->count() > 0) { instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
foreach ($containers as $container) { } else {
$containerName = data_get($container, 'Names'); $containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($containerName) { if ($containers->count() > 0) {
instant_remote_process( foreach ($containers as $container) {
["docker rm -f {$containerName}"], $containerName = data_get($container, 'Names');
$server if ($containerName) {
); instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
}
}
// 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);
} }
} }
// TODO: make notification for application
// $application->environment->project->team->notify(new StatusChanged($application));
} }
} }
} }

View File

@@ -39,7 +39,7 @@ class PrepareCoolifyTask
public function __invoke(): Activity 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); dispatch($job);
$this->activity->refresh(); $this->activity->refresh();
return $this->activity; return $this->activity;

View File

@@ -17,24 +17,24 @@ class RunRemoteProcess
public bool $hide_from_output; public bool $hide_from_output;
public bool $is_finished;
public bool $ignore_errors; public bool $ignore_errors;
public $call_event_on_finish = null;
protected $time_start; protected $time_start;
protected $current_time; protected $current_time;
protected $last_write_at = 0; protected $last_write_at = 0;
protected $throttle_interval_ms = 500; protected $throttle_interval_ms = 200;
protected int $counter = 1; protected int $counter = 1;
/** /**
* Create a new job instance. * 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) { if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
@@ -43,8 +43,8 @@ class RunRemoteProcess
$this->activity = $activity; $this->activity = $activity;
$this->hide_from_output = $hide_from_output; $this->hide_from_output = $hide_from_output;
$this->is_finished = $is_finished;
$this->ignore_errors = $ignore_errors; $this->ignore_errors = $ignore_errors;
$this->call_event_on_finish = $call_event_on_finish;
} }
public static function decodeOutput(?Activity $activity = null): string public static function decodeOutput(?Activity $activity = null): string
@@ -74,17 +74,29 @@ class RunRemoteProcess
$this->time_start = hrtime(true); $this->time_start = hrtime(true);
$status = ProcessStatus::IN_PROGRESS; $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) { if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
$status = ProcessStatus::ERROR; $status = ProcessStatus::ERROR;
} else { } else {
if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) { if ($processResult->exitCode() == 0) {
$status = ProcessStatus::FINISHED; $status = ProcessStatus::FINISHED;
} }
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
$status = ProcessStatus::ERROR; $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([ $this->activity->properties = $this->activity->properties->merge([
@@ -97,7 +109,15 @@ class RunRemoteProcess
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); 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; return $processResult;
} }
@@ -117,7 +137,6 @@ class RunRemoteProcess
} }
$this->current_time = $this->elapsedTime(); $this->current_time = $this->elapsedTime();
$this->activity->description = $this->encodeOutput($type, $output); $this->activity->description = $this->encodeOutput($type, $output);
if ($this->isAfterLastThrottle()) { if ($this->isAfterLastThrottle()) {
// Let's write to database. // Let's write to database.
DB::transaction(function () { DB::transaction(function () {

View File

@@ -23,7 +23,7 @@ class StartMariadb
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo '####### Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
]; ];
@@ -69,6 +69,16 @@ class StartMariadb
] ]
] ]
]; ];
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
} }
@@ -94,8 +104,8 @@ class StartMariadb
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $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 pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'"; $this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()

View File

@@ -25,7 +25,7 @@ class StartMongodb
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo '####### Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
]; ];
@@ -76,6 +76,16 @@ class StartMongodb
] ]
] ]
]; ];
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
} }
@@ -110,8 +120,8 @@ class StartMongodb
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $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 pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'"; $this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server); return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()

View File

@@ -23,7 +23,7 @@ class StartMysql
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo '####### Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
]; ];
@@ -69,6 +69,16 @@ class StartMysql
] ]
] ]
]; ];
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
} }
@@ -94,8 +104,8 @@ class StartMysql
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $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 pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'"; $this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server); return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()

View File

@@ -23,7 +23,7 @@ class StartPostgresql
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo '####### Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/" "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
]; ];
@@ -79,6 +79,17 @@ class StartPostgresql
] ]
] ]
]; ];
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
ray('Log Drain Enabled');
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
} }
@@ -119,8 +130,8 @@ class StartPostgresql
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $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 pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'"; $this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()

View File

@@ -26,7 +26,7 @@ class StartRedis
$this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [ $this->commands = [
"echo '####### Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
]; ];
@@ -78,6 +78,16 @@ class StartRedis
] ]
] ]
]; ];
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
];
}
if (count($this->database->ports_mappings_array) > 0) { if (count($this->database->ports_mappings_array) > 0) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
} }
@@ -104,8 +114,8 @@ class StartRedis
$this->commands[] = "echo 'Pulling {$database->image} image.'"; $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 pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'"; $this->commands[] = "echo '{$database->name} started.'";
return remote_process($this->commands, $database->destination->server); return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -156,6 +166,5 @@ class StartRedis
$content = $this->database->redis_conf; $content = $this->database->redis_conf;
$content_base64 = base64_encode($content); $content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}"; $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
} }
} }

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb; use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql; use App\Models\StandaloneMysql;

View File

@@ -17,35 +17,42 @@ class CheckProxy
return false; return false;
} }
} }
$status = getContainerStatus($server, 'coolify-proxy'); if ($server->isSwarm()) {
if ($status === 'running') { $status = getContainerStatus($server, 'coolify-proxy_traefik');
$server->proxy->set('status', 'running'); $server->proxy->set('status', $status);
$server->save(); $server->save();
return false; return false;
} } else {
$ip = $server->ip; $status = getContainerStatus($server, 'coolify-proxy');
if ($server->id === 0) { if ($status === 'running') {
$ip = 'host.docker.internal'; $server->proxy->set('status', 'running');
} $server->save();
return false;
}
$ip = $server->ip;
if ($server->id === 0) {
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80'); $connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443'); $connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80); $port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443); $port443 = is_resource($connection443) && fclose($connection443);
if ($port80) { if ($port80) {
if ($fromUI) { if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>"); throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else { } else {
return false; return false;
}
} }
} if ($port443) {
if ($port443) { if ($fromUI) {
if ($fromUI) { throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>"); } else {
} else { return false;
return false; }
} }
return true;
} }
return true;
} }
} }

View File

@@ -13,6 +13,7 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity public function handle(Server $server, bool $async = true): string|Activity
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
$commands = collect([]); $commands = collect([]);
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
@@ -24,18 +25,29 @@ class StartProxy
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
$commands = $commands->merge([ if ($server->isSwarm()) {
"mkdir -p $proxy_path && cd $proxy_path", $commands = $commands->merge([
"echo 'Creating required Docker Compose file.'", "mkdir -p $proxy_path && cd $proxy_path",
"echo 'Pulling docker image.'", "echo 'Creating required Docker Compose file.'",
'docker compose pull', "echo 'Starting coolify-proxy.'",
"echo 'Stopping existing coolify-proxy.'", "cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
"docker compose down -v --remove-orphans > /dev/null 2>&1", "echo 'Proxy started successfully.'"
"echo 'Starting coolify-proxy.'", ]);
'docker compose up -d --remove-orphans', } else {
"echo 'Proxy started successfully.'" $commands = $commands->merge([
]); "mkdir -p $proxy_path && cd $proxy_path",
$commands = $commands->merge(connectProxyToNetworks($server)); "echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
"echo 'Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}
if ($async) { if ($async) {
$activity = remote_process($commands, $server); $activity = remote_process($commands, $server);
return $activity; return $activity;
@@ -46,11 +58,9 @@ class StartProxy
$server->save(); $server->save();
return 'OK'; return 'OK';
} }
} catch(\Throwable $e) { } catch (\Throwable $e) {
ray($e); ray($e);
throw $e; throw $e;
} }
} }
} }

View File

@@ -11,6 +11,11 @@ class InstallDocker
use AsAction; use AsAction;
public function handle(Server $server) public function handle(Server $server)
{ {
$supported_os_type = $server->validateOS();
if (!$supported_os_type) {
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/servers#install-docker-engine-manually">documentation</a>.');
}
ray('Installing Docker on server: ' . $server->name . ' (' . $server->ip . ')' . ' with OS type: ' . $supported_os_type);
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{
"log-driver": "json-file", "log-driver": "json-file",
@@ -27,36 +32,65 @@ class InstallDocker
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
} }
$command = collect([]);
if (isDev() && $server->id === 0) { if (isDev() && $server->id === 0) {
$command = [ $command = $command->merge([
"echo '####### Installing Prerequisites...'", "echo 'Installing Prerequisites...'",
"sleep 1", "sleep 1",
"echo '####### Installing/updating Docker Engine...'", "echo 'Installing Docker Engine...'",
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'", "echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"sleep 4", "sleep 4",
"echo '####### Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
"ls -l /tmp" "ls -l /tmp"
]; ]);
} else { } else {
$command = [ if ($supported_os_type->contains('debian')) {
"echo '####### Installing Prerequisites...'", $command = $command->merge([
"command -v jq >/dev/null || apt-get update", "echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || apt install -y jq", "command -v jq >/dev/null || apt-get update -y",
"echo '####### Installing/updating Docker Engine...'", "command -v jq >/dev/null || apt install -y curl wget git jq",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'", ]);
} else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || dnf install -y curl wget git jq",
]);
} else if ($supported_os_type->contains('sles')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || zypper update -y",
"command -v jq >/dev/null || zypper install -y curl wget git jq",
]);
} else {
throw new \Exception('Unsupported OS');
}
$command = $command->merge([
"echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json", "test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify", "echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify", "cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json", "cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
"echo '####### Restarting Docker Engine...'", "echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker", "systemctl restart docker",
"echo '####### Creating default Docker network (coolify)...'", ]);
"docker network create --attachable coolify >/dev/null 2>&1 || true", if ($server->isSwarm()) {
"echo '####### Done!'" $command = $command->merge([
]; "docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
]);
} else {
$command = $command->merge([
"docker network create --attachable coolify >/dev/null 2>&1 || true",
]);
$command = $command->merge([
"echo 'Done!'",
]);
}
return remote_process($command, $server);
} }
return remote_process($command, $server);
} }
} }

View File

@@ -0,0 +1,211 @@
<?php
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class InstallLogDrain
{
use AsAction;
public function handle(Server $server)
{
if ($server->settings->is_logdrain_newrelic_enabled) {
$type = 'newrelic';
} else if ($server->settings->is_logdrain_highlight_enabled) {
$type = 'highlight';
} else if ($server->settings->is_logdrain_axiom_enabled) {
$type = 'axiom';
} else if ($server->settings->is_logdrain_custom_enabled) {
$type = 'custom';
} else {
$type = 'none';
}
try {
if ($type === 'none') {
$command = [
"echo 'Stopping old Fluent Bit'",
"docker rm -f coolify-log-drain || true",
];
return instant_remote_process($command, $server);
} else if ($type === 'newrelic') {
if (!$server->settings->is_logdrain_newrelic_enabled) {
throw new \Exception('New Relic log drain is not enabled.');
}
$config = base64_encode("
[SERVICE]
Flush 5
Daemon off
Tag container_logs
Log_Level debug
Parsers_File parsers.conf
[INPUT]
Name forward
Buffer_Chunk_Size 1M
Buffer_Max_Size 6M
[FILTER]
Name grep
Match *
Exclude log 127.0.0.1
[FILTER]
Name modify
Match *
Set server_name {$server->name}
[OUTPUT]
Name nrlogs
Match *
license_key \${LICENSE_KEY}
# https://log-api.eu.newrelic.com/log/v1 - EU
# https://log-api.newrelic.com/log/v1 - US
base_uri \${BASE_URI}
");
} else if ($type === 'highlight') {
if (!$server->settings->is_logdrain_highlight_enabled) {
throw new \Exception('Highlight log drain is not enabled.');
}
$config = base64_encode("
[SERVICE]
Flush 5
Daemon off
Log_Level debug
Parsers_File parsers.conf
[INPUT]
Name forward
tag \${HIGHLIGHT_PROJECT_ID}
Buffer_Chunk_Size 1M
Buffer_Max_Size 6M
[OUTPUT]
Name forward
Match *
Host otel.highlight.io
Port 24224
");
} else if ($type === 'axiom') {
if (!$server->settings->is_logdrain_axiom_enabled) {
throw new \Exception('Axiom log drain is not enabled.');
}
$config = base64_encode("
[SERVICE]
Flush 5
Daemon off
Log_Level debug
Parsers_File parsers.conf
[INPUT]
Name forward
Buffer_Chunk_Size 1M
Buffer_Max_Size 6M
[FILTER]
Name grep
Match *
Exclude log 127.0.0.1
[FILTER]
Name modify
Match *
Set server_name {$server->name}
[OUTPUT]
Name http
Match *
Host api.axiom.co
Port 443
URI /v1/datasets/\${AXIOM_DATASET_NAME}/ingest
# Authorization Bearer should be an API token
Header Authorization Bearer \${AXIOM_API_KEY}
compress gzip
format json
json_date_key _time
json_date_format iso8601
tls On
");
} else if ($type === 'custom') {
if (!$server->settings->is_logdrain_custom_enabled) {
throw new \Exception('Custom log drain is not enabled.');
}
$config = base64_encode($server->settings->logdrain_custom_config);
$parsers = base64_encode($server->settings->logdrain_custom_config_parser);
} else {
throw new \Exception('Unknown log drain type.');
}
if ($type !== 'custom') {
$parsers = base64_encode("
[PARSER]
Name empty_line_skipper
Format regex
Regex /^(?!\s*$).+/
");
}
$compose = base64_encode("
services:
coolify-log-drain:
image: cr.fluentbit.io/fluent/fluent-bit:2.0
container_name: coolify-log-drain
command: -c /fluent-bit.conf
env_file:
- .env
volumes:
- ./fluent-bit.conf:/fluent-bit.conf
- ./parsers.conf:/parsers.conf
ports:
- 127.0.0.1:24224:24224
restart: unless-stopped
");
$readme = base64_encode('# New Relic Log Drain
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
Files:
- `fluent-bit.conf` - configuration file for Fluent Bit
- `docker-compose.yml` - docker-compose file to run Fluent Bit
- `.env` - environment variables for Fluent Bit
');
$license_key = $server->settings->logdrain_newrelic_license_key;
$base_uri = $server->settings->logdrain_newrelic_base_uri;
$base_path = config('coolify.base_config_path');
$config_path = $base_path . '/log-drains';
$fluent_bit_config = $config_path . '/fluent-bit.conf';
$parsers_config = $config_path . '/parsers.conf';
$compose_path = $config_path . '/docker-compose.yml';
$readme_path = $config_path . '/README.md';
$command = [
"echo 'Saving configuration'",
"mkdir -p $config_path",
"echo '{$parsers}' | base64 -d > $parsers_config",
"echo '{$config}' | base64 -d > $fluent_bit_config",
"echo '{$compose}' | base64 -d > $compose_path",
"echo '{$readme}' | base64 -d > $readme_path",
"test -f $config_path/.env && rm $config_path/.env",
];
if ($type === 'newrelic') {
$add_envs_command = [
"echo LICENSE_KEY=$license_key >> $config_path/.env",
"echo BASE_URI=$base_uri >> $config_path/.env",
];
} else if ($type === 'highlight') {
$add_envs_command = [
"echo HIGHLIGHT_PROJECT_ID={$server->settings->logdrain_highlight_project_id} >> $config_path/.env",
];
} else if ($type === 'axiom') {
$add_envs_command = [
"echo AXIOM_DATASET_NAME={$server->settings->logdrain_axiom_dataset_name} >> $config_path/.env",
"echo AXIOM_API_KEY={$server->settings->logdrain_axiom_api_key} >> $config_path/.env",
];
} else if ($type === 'custom') {
$add_envs_command = [
"touch $config_path/.env"
];
} else {
throw new \Exception('Unknown log drain type.');
}
$restart_command = [
"echo 'Stopping old Fluent Bit'",
"cd $config_path && docker rm -f coolify-log-drain || true",
"echo 'Starting Fluent Bit'",
"cd $config_path && docker compose up -d --remove-orphans",
];
$command = array_merge($command, $add_envs_command, $restart_command);
return instant_remote_process($command, $server);
} catch (\Throwable $e) {
return handleError($e);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
class DeleteService
{
use AsAction;
public function handle(Service $service)
{
StopService::run($service);
$server = data_get($service, 'server');
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
$commands = [];
foreach ($service->applications()->get() as $application) {
$storages = $application->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
$application->delete();
}
foreach ($service->databases()->get() as $database) {
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
$storagesToDelete->push($storage);
}
$database->delete();
}
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
$commands[] = "docker rm -f $service->uuid";
instant_remote_process($commands, $server, false);
$service->forceDelete();
}
}

View File

@@ -14,21 +14,21 @@ class StartService
$network = $service->destination->network; $network = $service->destination->network;
$service->saveComposeConfigs(); $service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir(); $commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'"; $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Creating Docker network.'"; $commands[] = "echo 'Creating Docker network.'";
$commands[] = "docker network create --attachable '{$service->uuid}' >/dev/null || true"; $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'"; $commands[] = "echo 'Starting service $service->name on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'"; $commands[] = "echo 'Pulling images.'";
$commands[] = "docker compose pull"; $commands[] = "docker compose pull";
$commands[] = "echo '####### Starting containers.'"; $commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate"; $commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = "docker network connect $service->uuid coolify-proxy || true"; $commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
$compose = data_get($service,'docker_compose',[]); $compose = data_get($service, 'docker_compose', []);
$serviceNames = data_get(Yaml::parse($compose),'services',[]); $serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach($serviceNames as $serviceName => $serviceConfig){ foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true"; $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; return $activity;
} }
} }

View File

@@ -3,8 +3,11 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
@@ -14,6 +17,7 @@ use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
class Init extends Command class Init extends Command
@@ -23,30 +27,59 @@ class Init extends Command
public function handle() public function handle()
{ {
ray()->clearAll(); $this->alive();
$cleanup = $this->option('cleanup'); $cleanup = $this->option('cleanup');
if ($cleanup) { if ($cleanup) {
echo "Running cleanup\n";
$this->cleanup_stucked_resources(); $this->cleanup_stucked_resources();
$this->cleanup_ssh(); // $this->cleanup_ssh();
} }
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
} }
private function cleanup_stucked_helper_containers()
private function cleanup_ssh()
{ {
try { $servers = Server::all();
$files = Storage::allFiles('ssh/keys'); foreach ($servers as $server) {
foreach ($files as $file) { if ($server->isFunctional()) {
Storage::delete($file); CleanupHelperContainersJob::dispatch($server);
} }
$files = Storage::allFiles('ssh/mux');
foreach ($files as $file) {
Storage::delete($file);
}
} catch (\Throwable $e) {
echo "Error in cleaning ssh: {$e->getMessage()}\n";
} }
} }
private function alive()
{
$id = config('app.id');
$version = config('version');
$settings = InstanceSettings::get();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
return;
}
try {
Http::get("https://get.coollabs.io/coolify/v4/alive?appId=$id&version=$version");
echo "I am alive!\n";
} catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n";
}
}
// private function cleanup_ssh()
// {
// TODO: it will cleanup id.root@host.docker.internal
// try {
// $files = Storage::allFiles('ssh/keys');
// foreach ($files as $file) {
// Storage::delete($file);
// }
// $files = Storage::allFiles('ssh/mux');
// foreach ($files as $file) {
// Storage::delete($file);
// }
// } catch (\Throwable $e) {
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
// }
// }
private function cleanup_in_progress_application_deployments() private function cleanup_in_progress_application_deployments()
{ {
// Cleanup any failed deployments // Cleanup any failed deployments
@@ -68,15 +101,15 @@ class Init extends Command
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (!data_get($application, 'environment')) { if (!data_get($application, 'environment')) {
ray('Application without environment', $application->name); echo 'Application without environment' . $application->name . 'deleting\n';
$application->delete();
}
if (!data_get($application, 'destination.server')) {
ray('Application without server', $application->name);
$application->delete(); $application->delete();
} }
if (!$application->destination()) { if (!$application->destination()) {
ray('Application without destination', $application->name); 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(); $application->delete();
} }
} }
@@ -87,15 +120,15 @@ class Init extends Command
$postgresqls = StandalonePostgresql::all(); $postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) { if (!data_get($postgresql, 'environment')) {
ray('Postgresql without environment', $postgresql->name); echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
$postgresql->delete();
}
if (!data_get($postgresql, 'destination.server')) {
ray('Postgresql without server', $postgresql->name);
$postgresql->delete(); $postgresql->delete();
} }
if (!$postgresql->destination()) { if (!$postgresql->destination()) {
ray('Postgresql without destination', $postgresql->name); 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(); $postgresql->delete();
} }
} }
@@ -106,15 +139,15 @@ class Init extends Command
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) { if (!data_get($redis, 'environment')) {
ray('Redis without environment', $redis->name); echo 'Redis without environment' . $redis->name . 'deleting\n';
$redis->delete();
}
if (!data_get($redis, 'destination.server')) {
ray('Redis without server', $redis->name);
$redis->delete(); $redis->delete();
} }
if (!$redis->destination()) { if (!$redis->destination()) {
ray('Redis without destination', $redis->name); 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(); $redis->delete();
} }
} }
@@ -126,15 +159,15 @@ class Init extends Command
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) { if (!data_get($mongodb, 'environment')) {
ray('Mongodb without environment', $mongodb->name); echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
$mongodb->delete();
}
if (!data_get($mongodb, 'destination.server')) {
ray('Mongodb without server', $mongodb->name);
$mongodb->delete(); $mongodb->delete();
} }
if (!$mongodb->destination()) { if (!$mongodb->destination()) {
ray('Mongodb without destination', $mongodb->name); 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(); $mongodb->delete();
} }
} }
@@ -146,15 +179,15 @@ class Init extends Command
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) { if (!data_get($mysql, 'environment')) {
ray('Mysql without environment', $mysql->name); echo 'Mysql without environment' . $mysql->name . 'deleting\n';
$mysql->delete();
}
if (!data_get($mysql, 'destination.server')) {
ray('Mysql without server', $mysql->name);
$mysql->delete(); $mysql->delete();
} }
if (!$mysql->destination()) { if (!$mysql->destination()) {
ray('Mysql without destination', $mysql->name); 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(); $mysql->delete();
} }
} }
@@ -166,15 +199,15 @@ class Init extends Command
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) { if (!data_get($mariadb, 'environment')) {
ray('Mariadb without environment', $mariadb->name); echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
$mariadb->delete();
}
if (!data_get($mariadb, 'destination.server')) {
ray('Mariadb without server', $mariadb->name);
$mariadb->delete(); $mariadb->delete();
} }
if (!$mariadb->destination()) { if (!$mariadb->destination()) {
ray('Mariadb without destination', $mariadb->name); 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(); $mariadb->delete();
} }
} }
@@ -186,15 +219,15 @@ class Init extends Command
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (!data_get($service, 'environment')) { if (!data_get($service, 'environment')) {
ray('Service without environment', $service->name); echo 'Service without environment' . $service->name . 'deleting\n';
$service->delete();
}
if (!data_get($service, 'server')) {
ray('Service without server', $service->name);
$service->delete(); $service->delete();
} }
if (!$service->destination()) { if (!$service->destination()) {
ray('Service without destination', $service->name); 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(); $service->delete();
} }
} }
@@ -205,7 +238,7 @@ class Init extends Command
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
ray('ServiceApplication without service', $service->name); echo 'ServiceApplication without service' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
} }
@@ -216,7 +249,7 @@ class Init extends Command
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
ray('ServiceDatabase without service', $service->name); echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
$service->delete(); $service->delete();
} }
} }

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class RootChangeEmail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'root:change-email';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Change Root Email';
/**
* Execute the console command.
*/
public function handle()
{
//
$this->info('You are about to change the root user\'s email.');
$email = $this->ask('Give me a new email for root user');
$this->info('Updating root email...');
try {
User::find(0)->update(['email' => $email]);
$this->info('Root user\'s email updated successfully.');
} catch (\Exception $e) {
$this->error('Failed to update root user\'s email.');
return;
}
}
}

View File

@@ -8,14 +8,14 @@ use Illuminate\Support\Facades\Hash;
use function Laravel\Prompts\password; use function Laravel\Prompts\password;
class UsersResetRoot extends Command class RootResetPassword extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'users:reset-root'; protected $signature = 'root:reset-password';
/** /**
* The console command description. * The console command description.

View File

@@ -12,21 +12,21 @@ use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect; use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select; use function Laravel\Prompts\select;
class ResourcesDelete extends Command class ServicesDelete extends Command
{ {
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
* @var string * @var string
*/ */
protected $signature = 'resources:delete'; protected $signature = 'services:delete';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Delete a resource from the database'; protected $description = 'Delete a service from the database';
/** /**
* Execute the console command. * Execute the console command.
@@ -34,7 +34,7 @@ class ResourcesDelete extends Command
public function handle() public function handle()
{ {
$resource = select( $resource = select(
'What resource do you want to delete?', 'What service do you want to delete?',
['Application', 'Database', 'Service', 'Server'], ['Application', 'Database', 'Service', 'Server'],
); );
if ($resource === 'Application') { if ($resource === 'Application') {

View File

@@ -26,7 +26,7 @@ class ServicesGenerate extends Command
*/ */
public function handle() public function handle()
{ {
ray()->clearAll(); // ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']); $files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) { $files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false; return strpos($file, '.yaml') !== false;

View File

@@ -71,6 +71,15 @@ class SyncBunny extends Command
]); ]);
}); });
try { try {
if (!$only_template && !$only_version) {
$this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.');
}
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?'); $confirmed = confirm('Are you sure you want to sync?');
if (!$confirmed) { if (!$confirmed) {
return; return;

View File

@@ -2,16 +2,17 @@
namespace App\Console; namespace App\Console;
use App\Jobs\CheckResaleLicenseJob; use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob; use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob; use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob; use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\Server; use App\Models\Server;
use App\Models\Team;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -27,7 +28,6 @@ class Kernel extends ConsoleKernel
// Server Jobs // Server Jobs
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule); $this->pull_helper_image($schedule);
} else { } else {
@@ -40,33 +40,31 @@ class Kernel extends ConsoleKernel
$this->instance_auto_update($schedule); $this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->pull_helper_image($schedule); $this->pull_helper_image($schedule);
} }
} }
private function pull_helper_image($schedule) private function pull_helper_image($schedule)
{ {
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true); $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer(); $schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
} }
} }
private function cleanup_servers($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
}
private function check_resources($schedule) private function check_resources($schedule)
{ {
if (isCloud()) { if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false); $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);
} else { } else {
$servers = Server::all(); $servers = Server::all()->where('ip', '!=', '1.2.3.4');
} }
foreach ($servers as $server) { foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
}
} }
} }
private function instance_auto_update($schedule) private function instance_auto_update($schedule)

View File

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

View File

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

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

@@ -6,6 +6,7 @@ use App\Models\InstanceSettings;
use App\Models\User; use App\Models\User;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use RuntimeException;
use Sentry\Laravel\Integration; use Sentry\Laravel\Integration;
use Sentry\State\Scope; use Sentry\State\Scope;
use Throwable; use Throwable;
@@ -55,7 +56,9 @@ class Handler extends ExceptionHandler
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (isDev()) { if (isDev()) {
ray($e); // return;
}
if ($e instanceof RuntimeException) {
return; return;
} }
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
@@ -74,6 +77,7 @@ class Handler extends ExceptionHandler
); );
} }
); );
ray('reporting to sentry');
Integration::captureUnhandledException($e); Integration::captureUnhandledException($e);
}); });
} }

View File

@@ -10,23 +10,6 @@ class ApplicationController extends Controller
{ {
use AuthorizesRequests, ValidatesRequests; 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');
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return redirect()->route('dashboard');
}
return view('project.application.configuration', ['application' => $application]);
}
public function deployments() public function deployments()
{ {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();

View File

@@ -79,6 +79,10 @@ class Controller extends BaseController
if (isInstanceAdmin()) { if (isInstanceAdmin()) {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$database = StandalonePostgresql::whereName('coolify-db')->first(); $database = StandalonePostgresql::whereName('coolify-db')->first();
if ($database->status !== 'running') {
$database->status = 'running';
$database->save();
}
if ($database) { if ($database) {
$s3s = S3Storage::whereTeamId(0)->get(); $s3s = S3Storage::whereTeamId(0)->get();
} }

View File

@@ -67,7 +67,7 @@ class ProjectController extends Controller
$database = create_standalone_mongodb($environment->id, $destination_uuid); $database = create_standalone_mongodb($environment->id, $destination_uuid);
} else if ($type->value() === 'mysql') { } else if ($type->value() === 'mysql') {
$database = create_standalone_mysql($environment->id, $destination_uuid); $database = create_standalone_mysql($environment->id, $destination_uuid);
}else if ($type->value() === 'mariadb') { } else if ($type->value() === 'mariadb') {
$database = create_standalone_mariadb($environment->id, $destination_uuid); $database = create_standalone_mariadb($environment->id, $destination_uuid);
} }
return redirect()->route('project.database.configuration', [ return redirect()->route('project.database.configuration', [
@@ -104,27 +104,7 @@ class ProjectController extends Controller
$generatedValue = $value; $generatedValue = $value;
if ($value->contains('SERVICE_')) { if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_'); $command = $value->after('SERVICE_')->beforeLast('_');
// TODO: make it shared with Service.php $generatedValue = generateEnvValue($command->value());
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);
break;
case 'PASSWORD_64':
$generatedValue = Str::password(length: 64, symbols: false);
break;
case 'BASE64_64':
$generatedValue = Str::random(64);
break;
case 'BASE64_128':
$generatedValue = Str::random(128);
break;
case 'BASE64':
$generatedValue = Str::random(32);
break;
case 'USER':
$generatedValue = Str::random(16);
break;
}
} }
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
@@ -136,8 +116,7 @@ class ProjectController extends Controller
}); });
} }
$service->parse(isNew: true); $service->parse(isNew: true);
return redirect()->route('project.service.configuration', [
return redirect()->route('project.service', [
'service_uuid' => $service->uuid, 'service_uuid' => $service->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,

View File

@@ -1,82 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Jobs\ContainerStatusJob;
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();
});
}
}
public function force_deploy_without_cache()
{
$this->deploy(force_rebuild: true);
}
public function deploy(bool $force_rebuild = false)
{
$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,71 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Support\Str;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Rollback extends Component
{
public Application $application;
public $images = [];
public string|null $current;
public array $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
}
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $deployment_uuid,
commit: $commit,
force_rebuild: false,
);
return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $deployment_uuid,
'environment_name' => $this->parameters['environment_name'],
]);
}
public function loadImages()
{
try {
$image = $this->application->uuid;
$output = instant_remote_process([
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
], $this->application->destination->server, throwError: false);
$current_tag = Str::of($output)->trim()->explode(":");
$this->current = data_get($current_tag, 1);
$output = instant_remote_process([
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
], $this->application->destination->server);
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
return Str::of($item)->contains($image);
})->map(function ($item) {
$item = Str::of($item)->explode('#');
if ($item[1] === $this->current) {
// $is_current = true;
}
return [
'tag' => $item[1],
'created_at' => $item[2],
'is_current' => $is_current ?? null,
];
})->toArray();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -1,141 +0,0 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Service;
use Livewire\Component;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class DockerCompose extends Component
{
public string $dockerComposeRaw = '';
public string $envFile = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (isDev()) {
$this->dockerComposeRaw = 'services:
ghost:
image: ghost:5
volumes:
- ~/configs:/etc/configs/:ro
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
- /var/lib/ghost/content:/tmp/ghost/content:rw
- ghost-content-data:/var/lib/ghost/content
- type: volume
source: mydata
target: /data
- type: bind
source: ./var/lib/ghost/data
target: /data
- type: bind
source: /tmp
target: /tmp
labels:
- "test.label=true"
ports:
- "3000"
- "3000-3005"
- "8000:8000"
- "9090-9091:8080-8081"
- "49100:22"
- "127.0.0.1:8001:8001"
- "127.0.0.1:5000-5010:5000-5010"
- "127.0.0.1::5000"
- "6060:6060/udp"
- "12400-12500:1240"
- target: 80
published: 8080
protocol: tcp
mode: host
networks:
- some-network
- other-network
environment:
- database__client=${DATABASE_CLIENT:-mysql}
- database__connection__database=${MYSQL_DATABASE:-ghost}
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
- test=${TEST:?true}
- url=$SERVICE_FQDN_GHOST
- database__connection__user=$SERVICE_USER_MYSQL
- database__connection__password=$SERVICE_PASSWORD_MYSQL
depends_on:
- mysql
mysql:
image: mysql:8.0
volumes:
- ghost-mysql-data:/var/lib/mysql
environment:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=$MYSQL_DATABASE
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
- SESSION_SECRET
minio:
image: minio/minio
environment:
RACK_ENV: development
A: $A
SHOW: ${SHOW}
SHOW1: ${SHOW2-show1}
SHOW2: ${SHOW3:-show2}
SHOW3: ${SHOW4?show3}
SHOW4: ${SHOW5:?show4}
SHOW5: ${SERVICE_USER_MINIO}
SHOW6: ${SERVICE_PASSWORD_MINIO}
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
SHOW8: ${SERVICE_BASE64_64_MINIO}
SHOW9: ${SERVICE_BASE64_128_MINIO}
SHOW10: ${SERVICE_BASE64_MINIO}
SHOW11:
';
}
}
public function submit()
{
try {
$this->validate([
'dockerComposeRaw' => 'required'
]);
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$server_id = $this->query['server_id'];
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$service = Service::create([
'name' => 'service' . Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
]);
$variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) {
EnvironmentVariable::create([
'key' => $key,
'value' => $variable,
'is_build_time' => false,
'is_preview' => false,
'service_id' => $service->id,
]);
}
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
return redirect()->route('project.service', [
'service_uuid' => $service->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
} 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,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,10 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class Destination extends Component
{
public $destination;
}

View File

@@ -1,52 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use App\Models\Server;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
class GetLogs extends Component
{
public string $outputs = '';
public string $errors = '';
public Server $server;
public ?string $container = null;
public ?bool $streamLogs = false;
public ?bool $showTimeStamps = true;
public int $numberOfLines = 100;
public function doSomethingWithThisChunkOfOutput($output)
{
$this->outputs .= removeAnsiColors($output);
}
public function instantSave()
{
}
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,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');
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Jobs;
use App\Traits\ExecuteRemoteCommand;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
public $timeout = 3600;
public $tries = 1;
public string $applicationDeploymentQueueId;
public function __construct(string $applicationDeploymentQueueId)
{
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
}
public function handle() {
ray('Restarting application');
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Jobs;
use App\Actions\Server\InstallLogDrain;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
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\Sleep;
class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function healthcheck()
{
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
if (str($status)->contains('running')) {
return true;
} else {
return false;
}
}
public function handle(): void
{
// ray("checking log drain statuses for {$this->server->id}");
try {
if (!$this->server->isServerReady()) {
return;
};
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
if (!$containers) {
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
$containers = format_docker_command_output_to_json($containers);
$foundLogDrainContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-log-drain';
})->first();
if (!$foundLogDrainContainer || !$this->healthcheck()) {
ray('Log drain container not found or unhealthy. Restarting...');
InstallLogDrain::run($this->server);
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->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->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->update(['log_drain_notification_sent' => false]);
}
}
} catch (\Throwable $e) {
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
handleError($e);
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
}
public function handle(): void
{
try {
ray('Cleaning up helper containers on ' . $this->server->name);
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
$containers = format_docker_command_output_to_json($containers);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerId = data_get($container,'ID');
ray('Removing container ' . $containerId);
instant_remote_process(['docker container rm -f ' . $containerId], $this->server, false);
}
}
} catch (\Throwable $e) {
send_internal_notification('CleanupHelperContainersJob failed with error: ' . $e->getMessage());
ray($e->getMessage());
}
}
}

View File

@@ -2,14 +2,13 @@
namespace App\Jobs; namespace App\Jobs;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@@ -23,10 +22,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
$this->handle();
}
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->id))->dontRelease()]; return [(new WithoutOverlapping($this->server->id))->dontRelease()];
@@ -37,123 +32,75 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return $this->server->id; return $this->server->id;
} }
public function handle(): void public function __construct(public Server $server)
{ {
// ray("checking server status for {$this->server->id}"); $this->handle();
}
public function handle()
{
// ray("checking container statuses for {$this->server->id}");
try { try {
// ray()->clearAll(); if (!$this->server->isServerReady()) {
$serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3;
// ray('checking # ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...');
$this->server->team->notify(new Unreachable($this->server));
$this->server->update(['unreachable_email_sent' => true]);
}
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => 0,
]);
// Update all applications, databases and services to exited
foreach ($this->server->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->server->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->server->services() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
return; return;
} };
$result = $this->server->validateConnection(); if ($this->server->isSwarm()) {
if ($result) { $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
$this->server->settings()->update([ $containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
'is_reachable' => true,
]);
$this->server->update([
'unreachable_count' => 0,
]);
} else { } else {
$serverUptimeCheckNumber++; // Precheck for containers
$this->server->settings()->update([ $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
'is_reachable' => false, if (!$containers) {
]); return;
$this->server->update([ }
'unreachable_count' => $serverUptimeCheckNumber, $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
]); $containerReplicase = null;
}
if (is_null($containers)) {
return; return;
} }
if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...');
$this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]);
}
if (
data_get($this->server, 'settings.is_reachable') === false ||
data_get($this->server, 'settings.is_usable') === false
) {
$this->server->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
// $this->server->validateDockerEngine(true);
$containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) {
return;
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
if ($containerReplicase) {
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
foreach ($containerReplicase as $containerReplica) {
$name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) {
$replicas = data_get($containerReplica, 'Replicas');
$running = str($replicas)->explode('/')[0];
$total = str($replicas)->explode('/')[1];
if ($running === $total) {
data_set($container, 'State.Status', 'running');
data_set($container, 'State.Health.Status', 'healthy');
} else {
data_set($container, 'State.Status', 'starting');
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
}
$applications = $this->server->applications(); $applications = $this->server->applications();
$databases = $this->server->databases(); $databases = $this->server->databases();
$services = $this->server->services()->get(); $services = $this->server->services()->get();
$previews = $this->server->previews(); $previews = $this->server->previews();
$this->server->proxyType();
/// Check if proxy is running
$foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy';
})->first();
if (!$foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
} else {
ray('Proxy could not be started.');
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
$foundApplications = []; $foundApplications = [];
$foundApplicationPreviews = []; $foundApplicationPreviews = [];
$foundDatabases = []; $foundDatabases = [];
$foundServices = []; $foundServices = [];
foreach ($containers as $container) { foreach ($containers as $container) {
if ($this->server->isSwarm()) {
$labels = data_get($container, 'Spec.Labels');
$uuid = data_get($labels, 'coolify.name');
} else {
$labels = data_get($container, 'Config.Labels');
}
$containerStatus = data_get($container, 'State.Status'); $containerStatus = data_get($container, 'State.Status');
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
$containerStatus = "$containerStatus ($containerHealth)"; $containerStatus = "$containerStatus ($containerHealth)";
$labels = data_get($container, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels)); $labels = Arr::undot(format_docker_labels_to_json($labels));
$applicationId = data_get($labels, 'coolify.applicationId'); $applicationId = data_get($labels, 'coolify.applicationId');
if ($applicationId) { if ($applicationId) {
@@ -189,11 +136,25 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if ($uuid) { if ($uuid) {
$database = $databases->where('uuid', $uuid)->first(); $database = $databases->where('uuid', $uuid)->first();
if ($database) { if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id; $foundDatabases[] = $database->id;
$statusFromDb = $database->status; $statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) { if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]); $database->update(['status' => $containerStatus]);
} }
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else { } else {
// Notify user that this container should not be there. // Notify user that this container should not be there.
} }
@@ -258,7 +219,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$url = null; $url = null;
} }
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']); $exitedService->update(['status' => 'exited']);
} }
@@ -285,7 +246,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$url = null; $url = null;
} }
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
} }
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) { foreach ($notRunningApplicationPreviews as $previewId) {
@@ -310,7 +271,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$url = null; $url = null;
} }
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
} }
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) { foreach ($notRunningDatabases as $database) {
@@ -334,12 +295,38 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$url = null; $url = null;
} }
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
$foundProxyContainer = $containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
} else {
return data_get($value, 'Name') === '/coolify-proxy';
}
})->first();
if (!$foundProxyContainer) {
try {
$shouldStart = CheckProxy::run($this->server);
if ($shouldStart) {
StartProxy::run($this->server, false);
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} catch (\Throwable $e) {
ray($e);
}
} else {
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage()); send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
handleError($e); return handleError($e);
} }
} }
} }

View File

@@ -21,6 +21,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
public function __construct( public function __construct(
public Activity $activity, public Activity $activity,
public bool $ignore_errors = false, 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, [ $remote_process = resolve(RunRemoteProcess::class, [
'activity' => $this->activity, 'activity' => $this->activity,
'ignore_errors' => $this->ignore_errors, 'ignore_errors' => $this->ignore_errors,
'call_event_on_finish' => $this->call_event_on_finish
]); ]);
$remote_process(); $remote_process();

View File

@@ -3,6 +3,7 @@
namespace App\Jobs; namespace App\Jobs;
use App\Actions\Database\StopDatabase; use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\S3Storage; use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution; use App\Models\ScheduledDatabaseBackupExecution;
@@ -74,6 +75,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void public function handle(): void
{ {
try { try {
BackupCreated::dispatch($this->team->id);
// Check if team is exists // Check if team is exists
if (is_null($this->team)) { if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']); $this->backup->update(['status' => 'failed']);
@@ -307,6 +309,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
throw $e; throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
} }
} }
private function backup_standalone_mongodb(string $databaseWithCollections): void private function backup_standalone_mongodb(string $databaseWithCollections): void
@@ -434,7 +438,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
// $region = $this->s3->region; // $region = $this->s3->region;
$bucket = $this->s3->bucket; $bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint; $endpoint = $this->s3->endpoint;
$this->s3->testConnection(); $this->s3->testConnection(shouldSave: true);
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') { if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$network = $this->database->service->destination->network; $network = $this->database->service->destination->network;
} else { } else {

View File

@@ -4,7 +4,7 @@ namespace App\Jobs;
use App\Actions\Application\StopApplication; use App\Actions\Application\StopApplication;
use App\Actions\Database\StopDatabase; use App\Actions\Database\StopDatabase;
use App\Actions\Service\StopService; use App\Actions\Service\DeleteService;
use App\Models\Application; use App\Models\Application;
use App\Models\Service; use App\Models\Service;
use App\Models\StandaloneMariadb; use App\Models\StandaloneMariadb;
@@ -54,11 +54,13 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
case 'standalone-mariadb': case 'standalone-mariadb':
StopDatabase::run($this->resource); StopDatabase::run($this->resource);
break; break;
case 'service':
StopService::run($this->resource);
break;
} }
$this->resource->delete(); if ($this->resource->type() === 'service') {
$this->resource->delete();
DeleteService::dispatch($this->resource);
} else {
$this->resource->delete();
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage()); send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
throw $e; throw $e;

View File

@@ -3,7 +3,6 @@
namespace App\Jobs; namespace App\Jobs;
use App\Models\Server; use App\Models\Server;
use Exception;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@@ -12,51 +11,42 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use RuntimeException;
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 300; public $timeout = 300;
public ?string $dockerRootFilesystem = null;
public ?int $usageBefore = null; public ?int $usageBefore = null;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
} }
public function handle(): void public function handle(): void
{ {
$isInprogress = false;
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
if ($isInprogress) {
throw new Exception('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
}
try { try {
$isInprogress = false;
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
if ($isInprogress) {
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
}
if (!$this->server->isFunctional()) { if (!$this->server->isFunctional()) {
return; return;
} }
$this->dockerRootFilesystem = "/"; $this->usageBefore = $this->server->getDiskUsage();
$this->usageBefore = $this->getFilesystemUsage(); ray('Usage before: ' . $this->usageBefore);
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name); ray('Cleaning up ' . $this->server->name);
instant_remote_process(['docker image prune -af'], $this->server); instant_remote_process(['docker image prune -af'], $this->server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server); instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server, false);
instant_remote_process(['docker builder prune -af'], $this->server); instant_remote_process(['docker builder prune -af'], $this->server, false);
$usageAfter = $this->getFilesystemUsage(); $usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) { if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name); 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); send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
@@ -74,9 +64,4 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
throw $e; throw $e;
} }
} }
private function getFilesystemUsage()
{
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false);
}
} }

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use App\Models\Server;
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 ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?int $disk_usage = null;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function handle(): void
{
ray("checking server status for {$this->server->id}");
try {
if ($this->server->isServerReady()) {
$this->cleanup(notify: false);
}
} catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
handleError($e);
}
}
public function cleanup(bool $notify = false): void
{
$this->disk_usage = $this->server->getDiskUsage();
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
if ($notify) {
if ($this->server->high_disk_usage_notification_sent) {
ray('high disk usage notification already sent');
return;
} 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));
}
} else {
DockerCleanupJob::dispatchSync($this->server);
$this->cleanup(notify: true);
}
} else {
$this->server->high_disk_usage_notification_sent = false;
$this->server->save();
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Boarding; namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker; use App\Actions\Server\InstallDocker;
use App\Models\PrivateKey; use App\Models\PrivateKey;
@@ -31,9 +31,11 @@ class Index extends Component
public ?string $remoteServerHost = null; public ?string $remoteServerHost = null;
public ?int $remoteServerPort = 22; public ?int $remoteServerPort = 22;
public ?string $remoteServerUser = 'root'; public ?string $remoteServerUser = 'root';
public bool $isSwarmManager = false;
public bool $isCloudflareTunnel = false;
public ?Server $createdServer = null; public ?Server $createdServer = null;
public Collection|array $projects = []; public Collection $projects;
public ?int $selectedExistingProject = null; public ?int $selectedExistingProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
@@ -86,7 +88,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
if ($this->selectedServerType === 'localhost') { if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
$this->serverPublicKey = $this->createdServer->privateKey->publicKey(); $this->serverPublicKey = $this->createdServer->privateKey->publicKey();
return $this->validateServer('localhost'); return $this->validateServer('localhost');
@@ -108,7 +110,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{ {
$this->createdServer = Server::find($this->selectedExistingServer); $this->createdServer = Server::find($this->selectedExistingServer);
if (!$this->createdServer) { if (!$this->createdServer) {
$this->emit('error', 'Server is not found.'); $this->dispatch('error', 'Server is not found.');
$this->currentState = 'private-key'; $this->currentState = 'private-key';
return; return;
} }
@@ -171,7 +173,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->privateKey = formatPrivateKey($this->privateKey); $this->privateKey = formatPrivateKey($this->privateKey);
$foundServer = Server::whereIp($this->remoteServerHost)->first(); $foundServer = Server::whereIp($this->remoteServerHost)->first();
if ($foundServer) { if ($foundServer) {
return $this->emit('error', 'IP address is already in use by another team.'); return $this->dispatch('error', 'IP address is already in use by another team.');
} }
$this->createdServer = Server::create([ $this->createdServer = Server::create([
'name' => $this->remoteServerName, 'name' => $this->remoteServerName,
@@ -182,13 +184,15 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'private_key_id' => $this->createdPrivateKey->id, 'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
$this->createdServer->save(); $this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->validateServer(); $this->validateServer();
} }
public function validateServer() public function validateServer()
{ {
try { try {
$customErrorMessage = "Server is not reachable:";
config()->set('coolify.mux_enabled', false); config()->set('coolify.mux_enabled', false);
instant_remote_process(['uptime'], $this->createdServer, true); instant_remote_process(['uptime'], $this->createdServer, true);
@@ -198,7 +202,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->serverReachable = false; $this->serverReachable = false;
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this); $this->createdServer->delete();
return handleError(error: $e, livewire: $this);
} }
try { try {
@@ -206,7 +211,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion); $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) { if (is_null($dockerVersion)) {
$this->currentState = 'install-docker'; $this->currentState = 'install-docker';
throw new \Exception('Docker version is not supported or not installed.'); throw new \Exception('Docker not found or old version is installed.');
} }
$this->createdServer->settings()->update([ $this->createdServer->settings()->update([
'is_usable' => true, 'is_usable' => true,
@@ -214,14 +219,20 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->getProxyType(); $this->getProxyType();
} catch (\Throwable $e) { } catch (\Throwable $e) {
// $this->dockerInstallationStarted = false; // $this->dockerInstallationStarted = false;
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this); return handleError(error: $e, livewire: $this);
} }
} }
public function installDocker() public function installDocker()
{ {
$this->dockerInstallationStarted = true; try {
$activity = InstallDocker::run($this->createdServer); $this->dockerInstallationStarted = true;
$this->emit('newMonitorActivity', $activity->id); $activity = InstallDocker::run($this->createdServer);
$this->dispatch('installDocker');
$this->dispatch('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
$this->dockerInstallationStarted = false;
return handleError(error: $e, livewire: $this);
}
} }
public function dockerInstalledOrSkipped() public function dockerInstalledOrSkipped()
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire; namespace App\Livewire;
use App\Actions\License\CheckResaleLicense; use App\Actions\License\CheckResaleLicense;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
@@ -33,11 +33,11 @@ class CheckLicense extends Component
if ($this->settings->resale_license) { if ($this->settings->resale_license) {
try { try {
CheckResaleLicense::run(); CheckResaleLicense::run();
$this->emit('reloadWindow'); $this->dispatch('reloadWindow');
} catch (\Throwable $e) { } catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage()); session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
return redirect()->to('/settings/license'); return $this->redirect('/settings/license', navigate: true);
} }
} }
} }

View File

@@ -1,9 +1,8 @@
<?php <?php
namespace App\Http\Livewire; namespace App\Livewire;
use App\Models\Project; use App\Models\Project;
use App\Models\S3Storage;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@@ -11,7 +10,6 @@ class Dashboard extends Component
{ {
public $projects = []; public $projects = [];
public $servers = []; public $servers = [];
public function mount() public function mount()
{ {
$this->servers = Server::ownedByCurrentTeam()->get(); $this->servers = Server::ownedByCurrentTeam()->get();

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Destination; namespace App\Livewire\Destination;
use Livewire\Component; use Livewire\Component;
@@ -30,13 +30,13 @@ class Form extends Component
try { try {
if ($this->destination->getMorphClass() === 'App\Models\StandaloneDocker') { if ($this->destination->getMorphClass() === 'App\Models\StandaloneDocker') {
if ($this->destination->attachedTo()) { if ($this->destination->attachedTo()) {
return $this->emit('error', 'You must delete all resources before deleting this destination.'); return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
} }
instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false); instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server); instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server);
} }
$this->destination->delete(); $this->destination->delete();
return redirect()->route('dashboard'); return $this->redirectRoute('dashboard', navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Destination\New; namespace App\Livewire\Destination\New;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker; use App\Models\StandaloneDocker as ModelsStandaloneDocker;
@@ -60,7 +60,7 @@ class StandaloneDocker extends Component
$found = $this->server->standaloneDockers()->where('network', $this->network)->first(); $found = $this->server->standaloneDockers()->where('network', $this->network)->first();
if ($found) { if ($found) {
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
$this->emit('error', 'Network already added to this server.'); $this->dispatch('error', 'Network already added to this server.');
return; return;
} else { } else {
$docker = ModelsStandaloneDocker::create([ $docker = ModelsStandaloneDocker::create([
@@ -70,7 +70,7 @@ class StandaloneDocker extends Component
]); ]);
} }
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
return redirect()->route('destination.show', $docker->uuid); return $this->redirectRoute('destination.show', $docker->uuid, navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Destination; namespace App\Livewire\Destination;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -21,7 +21,7 @@ class Show extends Component
return !$alreadyAddedNetworks->contains('network', $network['Name']); return !$alreadyAddedNetworks->contains('network', $network['Name']);
}); });
if ($this->networks->count() === 0) { if ($this->networks->count() === 0) {
$this->emit('success', 'No new networks found.'); $this->dispatch('success', 'No new networks found.');
} }
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Dev; namespace App\Livewire\Dev;
use Livewire\Component; use Livewire\Component;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire; namespace App\Livewire;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use DanHarrin\LivewireRateLimiting\WithRateLimiting; use DanHarrin\LivewireRateLimiting\WithRateLimiting;
@@ -35,7 +35,7 @@ class ForcePasswordReset extends Component
if ($firstLogin) { if ($firstLogin) {
send_internal_notification('First login for ' . auth()->user()->email); send_internal_notification('First login for ' . auth()->user()->email);
} }
return redirect()->route('dashboard'); return $this->redirectRoute('dashboard', navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire; namespace App\Livewire;
use DanHarrin\LivewireRateLimiting\WithRateLimiting; use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -42,7 +42,7 @@ class Help extends Component
); );
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}"); $mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io'); send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.'); $this->dispatch('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Livewire\Modal;
use App\Models\Service;
use LivewireUI\Modal\ModalComponent;
class EditCompose extends ModalComponent
{
public Service $service;
public $serviceId;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
];
public function mount() {
$this->service = Service::find($this->serviceId);
}
public function render()
{
return view('livewire.modal.edit-compose');
}
public function submit() {
$this->dispatch('warning', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
$this->closeModal();
}
}

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Notifications; namespace App\Livewire\Notifications;
use App\Models\Team; use App\Models\Team;
use App\Notifications\Test; use App\Notifications\Test;
@@ -47,12 +47,12 @@ class DiscordSettings extends Component
{ {
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
} }
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team->notify(new Test());
$this->emit('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Notifications; namespace App\Livewire\Notifications;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Team; use App\Models\Team;
@@ -63,7 +63,7 @@ class EmailSettings extends Component
]); ]);
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->dispatch('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -71,7 +71,7 @@ class EmailSettings extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test($this->emails)); $this->team->notify(new Test($this->emails));
$this->emit('success', 'Test Email sent successfully.'); $this->dispatch('success', 'Test Email sent successfully.');
} }
public function instantSaveInstance() public function instantSaveInstance()
{ {
@@ -83,7 +83,7 @@ class EmailSettings extends Component
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->dispatch('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -113,7 +113,7 @@ class EmailSettings extends Component
{ {
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
} }
public function submit() public function submit()
{ {
@@ -131,7 +131,7 @@ class EmailSettings extends Component
]); ]);
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->dispatch('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return handleError($e, $this); return handleError($e, $this);
@@ -148,7 +148,7 @@ class EmailSettings extends Component
]); ]);
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->dispatch('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
return handleError($e, $this); return handleError($e, $this);
@@ -173,7 +173,7 @@ class EmailSettings extends Component
]); ]);
refreshSession(); refreshSession();
$this->team = $team; $this->team = $team;
$this->emit('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
return; return;
} }
if ($settings->resend_enabled) { if ($settings->resend_enabled) {
@@ -184,9 +184,9 @@ class EmailSettings extends Component
]); ]);
refreshSession(); refreshSession();
$this->team = $team; $this->team = $team;
$this->emit('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
return; return;
} }
$this->emit('error', 'Instance SMTP/Resend settings are not enabled.'); $this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Notifications; namespace App\Livewire\Notifications;
use App\Models\Team; use App\Models\Team;
use App\Notifications\Test; use App\Notifications\Test;
@@ -53,12 +53,12 @@ class TelegramSettings extends Component
{ {
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
$this->emit('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
} }
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team->notify(new Test());
$this->emit('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\PrivateKey; namespace App\Livewire\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use Livewire\Component; use Livewire\Component;
@@ -35,9 +35,9 @@ class Change extends Component
if ($this->private_key->isEmpty()) { if ($this->private_key->isEmpty()) {
$this->private_key->delete(); $this->private_key->delete();
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get(); currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
return redirect()->route('security.private-key.index'); return $this->redirectRoute('security.private-key.index', navigate: true);
} }
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.'); $this->dispatch('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\PrivateKey; namespace App\Livewire\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting; use DanHarrin\LivewireRateLimiting\WithRateLimiting;
@@ -67,9 +67,9 @@ class Create extends Component
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
if ($this->from === 'server') { if ($this->from === 'server') {
return redirect()->route('server.create'); return $this->redirectRoute('server.create', navigate: true);
} }
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]); return $this->redirectRoute('security.private-key.show', ['private_key_uuid' => $private_key->uuid], navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,22 +1,17 @@
<?php <?php
namespace App\Http\Livewire\Profile; namespace App\Livewire\Profile;
use App\Models\User; use Livewire\Attributes\Validate;
use Livewire\Component; use Livewire\Component;
class Form extends Component class Form extends Component
{ {
public int $userId; public int $userId;
public string $name;
public string $email; public string $email;
protected $rules = [ #[Validate('required')]
'name' => 'required', public string $name;
];
protected $validationAttributes = [
'name' => 'name',
];
public function mount() public function mount()
{ {
@@ -30,9 +25,11 @@ class Form extends Component
{ {
try { try {
$this->validate(); $this->validate();
User::where('id', $this->userId)->update([ auth()->user()->update([
'name' => $this->name, 'name' => $this->name,
]); ]);
$this->dispatch('success', 'Profile updated successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project; namespace App\Livewire\Project;
use App\Models\Project; use App\Models\Project;
use Livewire\Component; use Livewire\Component;
@@ -27,7 +27,7 @@ class AddEmpty extends Component
'description' => $this->description, 'description' => $this->description,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
return redirect()->route('project.show', $project->uuid); return $this->redirectRoute('project.show', $project->uuid, navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project; namespace App\Livewire\Project;
use App\Models\Environment; use App\Models\Environment;
use App\Models\Project; use App\Models\Project;
@@ -27,10 +27,10 @@ class AddEnvironment extends Component
'project_id' => $this->project->id, 'project_id' => $this->project->id,
]); ]);
return redirect()->route('project.resources', [ return $this->redirectRoute('project.resources', [
'project_uuid' => $this->project->uuid, 'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ], navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
handleError($e, $this); handleError($e, $this);
} finally { } finally {

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Component;
class Advanced extends Component
{
public Application $application;
protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required',
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
'application.settings.is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
];
public function instantSave()
{
if ($this->application->isLogDrainEnabled()) {
if (!$this->application->destination->server->isLogDrainEnabled()) {
$this->application->settings->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on this server.');
return;
}
}
if ($this->application->settings->is_force_https_enabled) {
$this->dispatch('resetDefaultLabels', false);
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
}
public function submit() {
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
$this->application->settings->gpu_count = null;
$this->application->settings->gpu_device_ids = null;
$this->application->settings->save();
return;
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
}
public function render()
{
return view('livewire.project.application.advanced');
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Livewire\Component;
class Configuration extends Component
{
public Application $application;
public $servers;
public function mount()
{
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) {
return $this->redirectRoute('dashboard', navigate: true);
}
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) {
return $this->redirectRoute('dashboard', navigate: true);
}
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) {
return $this->redirectRoute('dashboard', navigate: true);
}
$this->application = $application;
$mainServer = $this->application->destination->server;
$servers = Server::ownedByCurrentTeam()->get();
$this->servers = $servers->filter(function ($server) use ($mainServer) {
return $server->id != $mainServer->id;
});
}
public function render()
{
return view('livewire.project.application.configuration');
}
}

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use Livewire\Component; use Livewire\Component;
@@ -18,7 +18,7 @@ class DeploymentLogs extends Component
public function polling() public function polling()
{ {
$this->emit('deploymentFinished'); $this->dispatch('deploymentFinished');
$this->application_deployment_queue->refresh(); $this->application_deployment_queue->refresh();
if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') { if (data_get($this->application_deployment_queue, 'status') == 'finished' || data_get($this->application_deployment_queue, 'status') == 'failed') {
$this->isKeepAliveOn = false; $this->isKeepAliveOn = false;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Models\Application; use App\Models\Application;
@@ -18,7 +18,6 @@ class DeploymentNavbar extends Component
public Server $server; public Server $server;
public bool $is_debug_enabled = false; public bool $is_debug_enabled = false;
protected $listeners = ['deploymentFinished']; protected $listeners = ['deploymentFinished'];
public function mount() public function mount()
{ {
$this->application = Application::find($this->application_deployment_queue->application_id); $this->application = Application::find($this->application_deployment_queue->application_id);
@@ -36,7 +35,7 @@ class DeploymentNavbar extends Component
$this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled; $this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled;
$this->application->settings->save(); $this->application->settings->save();
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->emit('refreshQueue'); $this->dispatch('refreshQueue');
} }
public function cancel() public function cancel()

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;

View File

@@ -1,11 +1,12 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2;
class General extends Component class General extends Component
{ {
@@ -25,15 +26,15 @@ class General extends Component
public bool $labelsChanged = false; public bool $labelsChanged = false;
public bool $isConfigurationChanged = false; public bool $isConfigurationChanged = false;
public bool $is_static; public ?string $initialDockerComposeLocation = null;
public bool $is_git_submodules_enabled; public ?string $initialDockerComposePrLocation = null;
public bool $is_git_lfs_enabled;
public bool $is_debug_enabled;
public bool $is_preview_deployments_enabled;
public bool $is_auto_deploy_enabled;
public bool $is_force_https_enabled;
public $parsedServices = [];
public $parsedServiceDomains = [];
protected $listeners = [
'resetDefaultLabels'
];
protected $rules = [ protected $rules = [
'application.name' => 'required', 'application.name' => 'required',
'application.description' => 'nullable', 'application.description' => 'nullable',
@@ -54,8 +55,15 @@ class General extends Component
'application.docker_registry_image_name' => 'nullable', 'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable', 'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable', 'application.dockerfile_location' => 'nullable',
'application.docker_compose_location' => 'nullable',
'application.docker_compose_pr_location' => 'nullable',
'application.docker_compose' => 'nullable',
'application.docker_compose_pr' => 'nullable',
'application.docker_compose_raw' => 'nullable',
'application.docker_compose_pr_raw' => 'nullable',
'application.custom_labels' => 'nullable', 'application.custom_labels' => 'nullable',
'application.dockerfile_target_build' => 'nullable', 'application.dockerfile_target_build' => 'nullable',
'application.settings.is_static' => 'boolean|required',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'application.name' => 'name', 'application.name' => 'name',
@@ -77,12 +85,26 @@ class General extends Component
'application.docker_registry_image_name' => 'Docker registry image name', 'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag', 'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location', 'application.dockerfile_location' => 'Dockerfile location',
'application.docker_compose_location' => 'Docker compose location',
'application.docker_compose_pr_location' => 'Docker compose location',
'application.docker_compose' => 'Docker compose',
'application.docker_compose_pr' => 'Docker compose',
'application.docker_compose_raw' => 'Docker compose raw',
'application.docker_compose_pr_raw' => 'Docker compose raw',
'application.custom_labels' => 'Custom labels', 'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build', 'application.dockerfile_target_build' => 'Dockerfile target build',
'application.settings.is_static' => 'Is static',
]; ];
public function mount() public function mount()
{ {
try {
$this->parsedServices = $this->application->parseCompose();
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) { if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
$this->application->isConfigurationChanged(true); $this->application->isConfigurationChanged(true);
@@ -93,21 +115,54 @@ class General extends Component
} else { } else {
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n"); $this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
} }
if (data_get($this->application, 'settings')) { $this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->is_static = $this->application->settings->is_static;
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
$this->checkLabelUpdates(); $this->checkLabelUpdates();
} }
public function instantSave()
{
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
}
public function loadComposeFile($isInit = false)
{
try {
if ($isInit && $this->application->docker_compose_raw) {
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$this->dispatch('success', 'Docker compose file loaded.');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
$this->application->save();
return handleError($e, $this);
}
}
public function generateDomain(string $serviceName)
{
$domain = $this->parsedServiceDomains[$serviceName]['domain'] ?? null;
if (!$domain) {
$uuid = new Cuid2(7);
$domain = generateFqdn($this->application->destination->server, $uuid);
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save();
$this->dispatch('success', 'Domain generated.');
}
return $domain;
}
public function updatedApplicationBuildPack() public function updatedApplicationBuildPack()
{ {
if ($this->application->build_pack !== 'nixpacks') { if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false; $this->application->settings->is_static = false;
$this->application->settings->save();
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->fqdn = null;
$this->application->settings->save(); $this->application->settings->save();
} }
$this->submit(); $this->submit();
@@ -120,32 +175,6 @@ class General extends Component
$this->labelsChanged = false; $this->labelsChanged = false;
} }
} }
public function instantSave()
{
// @TODO: find another way - if possible
$force_https = $this->application->settings->is_force_https_enabled;
$this->application->settings->is_static = $this->is_static;
if ($this->is_static) {
$this->application->ports_exposes = 80;
} else {
$this->application->ports_exposes = 3000;
}
$this->application->settings->is_git_submodules_enabled = $this->is_git_submodules_enabled;
$this->application->settings->is_git_lfs_enabled = $this->is_git_lfs_enabled;
$this->application->settings->is_debug_enabled = $this->is_debug_enabled;
$this->application->settings->is_preview_deployments_enabled = $this->is_preview_deployments_enabled;
$this->application->settings->is_auto_deploy_enabled = $this->is_auto_deploy_enabled;
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->application->settings->save();
$this->application->save();
$this->application->refresh();
$this->emit('success', 'Application settings updated!');
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if ($force_https !== $this->is_force_https_enabled) {
$this->resetDefaultLabels(false);
}
}
public function getWildcardDomain() public function getWildcardDomain()
{ {
@@ -167,11 +196,14 @@ class General extends Component
public function updatedApplicationFqdn() public function updatedApplicationFqdn()
{ {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
$this->emit('success', 'Labels reseted to default!'); $this->dispatch('success', 'Labels reseted to default!');
} }
public function submit($showToaster = true) public function submit($showToaster = true)
{ {
try { try {
if ($this->application->build_pack === 'dockercompose' && ($this->initialDockerComposeLocation !== $this->application->docker_compose_location || $this->initialDockerComposePrLocation !== $this->application->docker_compose_pr_location)) {
$this->loadComposeFile();
}
$this->validate(); $this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) { if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
@@ -188,6 +220,7 @@ class General extends Component
}); });
$this->application->fqdn = $domains->implode(','); $this->application->fqdn = $domains->implode(',');
} }
if (data_get($this->application, 'dockerfile')) { if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile); $port = get_port_from_dockerfile($this->application->dockerfile);
if ($port && !$this->application->ports_exposes) { if ($port && !$this->application->ports_exposes) {
@@ -204,8 +237,12 @@ class General extends Component
$this->customLabels = str($this->customLabels)->replace(',', "\n"); $this->customLabels = str($this->customLabels)->replace(',', "\n");
} }
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(','); $this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->parsedServices = $this->application->parseCompose();
}
$this->application->save(); $this->application->save();
$showToaster && $this->emit('success', 'Application settings updated!'); $showToaster && $this->dispatch('success', 'Application settings updated!');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Events\ApplicationStatusChanged;
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 getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
];
}
public function mount()
{
$this->parameters = get_route_parameters();
}
public function check_status($showNotification = false)
{
if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
}
if ($showNotification) $this->dispatch('success', 'Application status updated.');
}
public function force_deploy_without_cache()
{
$this->deploy(force_rebuild: true);
}
public function deployNew()
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->dispatch('error', 'Please load a Compose file first.');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
force_rebuild: false,
is_new_deployment: true,
);
return $this->redirectRoute('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'],
], navigate: true);
}
public function deploy(bool $force_rebuild = false)
{
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->dispatch('error', 'Please load a Compose file first.');
return;
}
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
$this->redirectRoute('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'],
], navigate: true);
}
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 restartNew()
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
is_new_deployment: true,
);
return $this->redirectRoute('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'],
], navigate: true);
}
public function restart()
{
$this->setDeploymentUuid();
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);
return $this->redirectRoute('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'],
], navigate: true);
}
}

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application\Preview; namespace App\Livewire\Project\Application\Preview;
use App\Models\Application; use App\Models\Application;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -46,7 +46,7 @@ class Form extends Component
$this->validate(); $this->validate();
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template); $this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
$this->application->save(); $this->application->save();
$this->emit('success', 'Preview url template updated successfully.'); $this->dispatch('success', 'Preview url template updated successfully.');
$this->generate_real_url(); $this->generate_real_url();
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
@@ -52,12 +52,12 @@ class Previews extends Component
force_rebuild: true, force_rebuild: true,
pull_request_id: $pull_request_id, pull_request_id: $pull_request_id,
); );
return redirect()->route('project.application.deployment', [ return $this->redirectRoute('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deployment_uuid, 'deployment_uuid' => $this->deployment_uuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
]); ], navigate: true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -72,10 +72,12 @@ class Previews extends Component
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
{ {
try { try {
$container_name = generateApplicationContainerName($this->application, $pull_request_id); $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); $name = str_replace('/', '', $container['Names']);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
$this->application->refresh(); $this->application->refresh();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Support\Str;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Rollback extends Component
{
public Application $application;
public $images = [];
public string|null $current;
public array $parameters;
public function mount()
{
$this->parameters = get_route_parameters();
}
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application_id: $this->application->id,
deployment_uuid: $deployment_uuid,
commit: $commit,
force_rebuild: false,
);
return $this->redirectRoute('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $deployment_uuid,
'environment_name' => $this->parameters['environment_name'],
], navigate: true);
}
public function loadImages($showToast = false)
{
try {
$image = $this->application->docker_registry_image_name ?? $this->application->uuid;
if ($this->application->destination->server->isFunctional()) {
$output = instant_remote_process([
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
], $this->application->destination->server, throwError: false);
$current_tag = Str::of($output)->trim()->explode(":");
$this->current = data_get($current_tag, 1);
$output = instant_remote_process([
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
], $this->application->destination->server);
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
return Str::of($item)->contains($image);
})->map(function ($item) {
$item = Str::of($item)->explode('#');
if ($item[1] === $this->current) {
// $is_current = true;
}
return [
'tag' => $item[1],
'created_at' => $item[2],
'is_current' => $is_current ?? null,
];
})->toArray();
}
$showToast && $this->dispatch('success', 'Images loaded.');
return [];
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\PrivateKey; use App\Models\PrivateKey;
@@ -49,6 +49,6 @@ class Source extends Component
$this->application->git_commit_sha = 'HEAD'; $this->application->git_commit_sha = 'HEAD';
} }
$this->application->save(); $this->application->save();
$this->emit('success', 'Application source updated!'); $this->dispatch('success', 'Application source updated!');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project; namespace App\Livewire\Project;
use App\Models\Environment; use App\Models\Environment;
use App\Models\Project; use App\Models\Project;
@@ -148,10 +148,10 @@ class CloneProject extends Component
} }
$newService->parse(); $newService->parse();
} }
return redirect()->route('project.resources', [ return $this->redirectRoute('project.resources', [
'project_uuid' => $newProject->uuid, 'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name, 'environment_name' => $newEnvironment->name,
]); ], navigate: true);
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use Livewire\Component; use Livewire\Component;
use Spatie\Url\Url; use Spatie\Url\Url;
@@ -50,9 +50,9 @@ class BackupEdit extends Component
$url = $url->withoutQueryParameter('selectedBackupId'); $url = $url->withoutQueryParameter('selectedBackupId');
$url = $url->withFragment('backups'); $url = $url->withFragment('backups');
$url = $url->getPath() . "#{$url->getFragment()}"; $url = $url->getPath() . "#{$url->getFragment()}";
return redirect()->to($url); return $this->redirect($url,navigate: true);
} else { } else {
redirect()->route('project.database.backups.all', $this->parameters); return $this->redirectRoute('project.database.backups.all', $this->parameters);
} }
} }
@@ -63,9 +63,9 @@ class BackupEdit extends Component
$this->custom_validate(); $this->custom_validate();
$this->backup->save(); $this->backup->save();
$this->backup->refresh(); $this->backup->refresh();
$this->emit('success', 'Backup updated successfully'); $this->dispatch('success', 'Backup updated successfully');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->emit('error', $e->getMessage()); $this->dispatch('error', $e->getMessage());
} }
} }
@@ -90,9 +90,9 @@ class BackupEdit extends Component
} }
$this->backup->save(); $this->backup->save();
$this->backup->refresh(); $this->backup->refresh();
$this->emit('success', 'Backup updated successfully'); $this->dispatch('success', 'Backup updated successfully');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->emit('error', $e->getMessage()); $this->dispatch('error', $e->getMessage());
} }
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Livewire\Component; use Livewire\Component;
@@ -10,13 +10,21 @@ class BackupExecutions extends Component
public $backup; public $backup;
public $executions; public $executions;
public $setDeletableBackup; public $setDeletableBackup;
protected $listeners = ['refreshBackupExecutions', 'deleteBackup']; public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
"refreshBackupExecutions",
"deleteBackup"
];
}
public function deleteBackup($exeuctionId) public function deleteBackup($exeuctionId)
{ {
$execution = $this->backup->executions()->where('id', $exeuctionId)->first(); $execution = $this->backup->executions()->where('id', $exeuctionId)->first();
if (is_null($execution)) { if (is_null($execution)) {
$this->emit('error', 'Backup execution not found.'); $this->dispatch('error', 'Backup execution not found.');
return; return;
} }
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
@@ -25,15 +33,15 @@ class BackupExecutions extends Component
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
} }
$execution->delete(); $execution->delete();
$this->emit('success', 'Backup deleted successfully.'); $this->dispatch('success', 'Backup deleted successfully.');
$this->emit('refreshBackupExecutions'); $this->dispatch('refreshBackupExecutions');
} }
public function download($exeuctionId) public function download($exeuctionId)
{ {
try { try {
$execution = $this->backup->executions()->where('id', $exeuctionId)->first(); $execution = $this->backup->executions()->where('id', $exeuctionId)->first();
if (is_null($execution)) { if (is_null($execution)) {
$this->emit('error', 'Backup execution not found.'); $this->dispatch('error', 'Backup execution not found.');
return; return;
} }
$filename = data_get($execution, 'filename'); $filename = data_get($execution, 'filename');

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use App\Jobs\DatabaseBackupJob; use App\Jobs\DatabaseBackupJob;
use Livewire\Component; use Livewire\Component;
@@ -13,6 +13,6 @@ class BackupNow extends Component
dispatch(new DatabaseBackupJob( dispatch(new DatabaseBackupJob(
backup: $this->backup backup: $this->backup
)); ));
$this->emit('success', 'Backup queued. It will be available in a few minutes.'); $this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use Livewire\Component; use Livewire\Component;
@@ -35,7 +35,7 @@ class CreateScheduledBackup extends Component
$this->validate(); $this->validate();
$isValid = validate_cron_expression($this->frequency); $isValid = validate_cron_expression($this->frequency);
if (!$isValid) { if (!$isValid) {
$this->emit('error', 'Invalid Cron / Human expression.'); $this->dispatch('error', 'Invalid Cron / Human expression.');
return; return;
} }
$payload = [ $payload = [
@@ -57,9 +57,9 @@ class CreateScheduledBackup extends Component
$databaseBackup = ScheduledDatabaseBackup::create($payload); $databaseBackup = ScheduledDatabaseBackup::create($payload);
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') { if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$this->emit('refreshScheduledBackups', $databaseBackup->id); $this->dispatch('refreshScheduledBackups', $databaseBackup->id);
} else { } else {
$this->emit('refreshScheduledBackups'); $this->dispatch('refreshScheduledBackups');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
handleError($e, $this); handleError($e, $this);

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use App\Actions\Database\StartMariadb; use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb; use App\Actions\Database\StartMongodb;
@@ -8,6 +8,7 @@ use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis; use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase; use App\Actions\Database\StopDatabase;
use App\Events\DatabaseStatusChanged;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use Livewire\Component; use Livewire\Component;
@@ -16,21 +17,28 @@ class Heading extends Component
public $database; public $database;
public array $parameters; public array $parameters;
protected $listeners = ['activityFinished']; public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished',
];
}
public function activityFinished() public function activityFinished()
{ {
$this->database->update([ $this->database->update([
'started_at' => now(), 'started_at' => now(),
]); ]);
$this->emit('refresh'); $this->dispatch('refresh');
$this->check_status(); $this->check_status();
} }
public function check_status() public function check_status($showNotification = false)
{ {
dispatch_sync(new ContainerStatusJob($this->database->destination->server)); dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh(); $this->database->refresh();
if ($showNotification) $this->dispatch('success', 'Database status updated.');
} }
public function mount() public function mount()
@@ -50,19 +58,19 @@ class Heading extends Component
{ {
if ($this->database->type() === 'standalone-postgresql') { if ($this->database->type() === 'standalone-postgresql') {
$activity = StartPostgresql::run($this->database); $activity = StartPostgresql::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-redis') { } else if ($this->database->type() === 'standalone-redis') {
$activity = StartRedis::run($this->database); $activity = StartRedis::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mongodb') { } else if ($this->database->type() === 'standalone-mongodb') {
$activity = StartMongodb::run($this->database); $activity = StartMongodb::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mysql') { } else if ($this->database->type() === 'standalone-mysql') {
$activity = StartMysql::run($this->database); $activity = StartMysql::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mariadb') { } else if ($this->database->type() === 'standalone-mariadb') {
$activity = StartMariadb::run($this->database); $activity = StartMariadb::run($this->database);
$this->emit('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id);
} }
} }
} }

View File

@@ -1,7 +1,8 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use Exception;
use Livewire\Component; use Livewire\Component;
class InitScript extends Component class InitScript extends Component
@@ -34,7 +35,7 @@ class InitScript extends Component
$this->script['index'] = $this->index; $this->script['index'] = $this->index;
$this->script['content'] = $this->content; $this->script['content'] = $this->content;
$this->script['filename'] = $this->filename; $this->script['filename'] = $this->filename;
$this->emitUp('save_init_script', $this->script); $this->dispatch('save_init_script', $this->script);
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -42,6 +43,6 @@ class InitScript extends Component
public function delete() public function delete()
{ {
$this->emitUp('delete_init_script', $this->script); $this->dispatch('delete_init_script', $this->script);
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database\Mariadb; namespace App\Livewire\Project\Database\Mariadb;
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
@@ -28,6 +28,7 @@ class General extends Component
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@@ -50,6 +51,20 @@ class General extends Component
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
} }
} }
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function submit() public function submit()
{ {
try { try {
@@ -58,7 +73,7 @@ class General extends Component
} }
$this->validate(); $this->validate();
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->dispatch('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -67,23 +82,23 @@ class General extends Component
{ {
try { try {
if ($this->database->is_public && !$this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);
$this->db_url_public = null; $this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.'); $this->dispatch('success', 'Database is no longer publicly accessible.');
} }
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database\Mongodb; namespace App\Livewire\Project\Database\Mongodb;
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
@@ -27,6 +27,7 @@ class General extends Component
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@@ -48,7 +49,21 @@ class General extends Component
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
} }
} }
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function submit() public function submit()
{ {
try { try {
@@ -60,7 +75,7 @@ class General extends Component
} }
$this->validate(); $this->validate();
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->dispatch('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -69,23 +84,23 @@ class General extends Component
{ {
try { try {
if ($this->database->is_public && !$this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);
$this->db_url_public = null; $this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.'); $this->dispatch('success', 'Database is no longer publicly accessible.');
} }
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database\Mysql; namespace App\Livewire\Project\Database\Mysql;
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
@@ -28,6 +28,7 @@ class General extends Component
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@@ -50,6 +51,21 @@ class General extends Component
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
} }
} }
public function instantSaveAdvanced()
{
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function submit() public function submit()
{ {
try { try {
@@ -58,7 +74,7 @@ class General extends Component
} }
$this->validate(); $this->validate();
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->dispatch('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -67,23 +83,23 @@ class General extends Component
{ {
try { try {
if ($this->database->is_public && !$this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);
$this->db_url_public = null; $this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.'); $this->dispatch('success', 'Database is no longer publicly accessible.');
} }
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database\Postgresql; namespace App\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
@@ -34,6 +34,7 @@ class General extends Component
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@@ -57,27 +58,41 @@ class General extends Component
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
} }
} }
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && !$this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);
$this->db_url_public = null; $this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.'); $this->dispatch('success', 'Database is no longer publicly accessible.');
} }
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -90,7 +105,7 @@ class General extends Component
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']); $this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
$this->database->init_scripts = array_merge($this->database->init_scripts, [$script]); $this->database->init_scripts = array_merge($this->database->init_scripts, [$script]);
$this->database->save(); $this->database->save();
$this->emit('success', 'Init script saved successfully.'); $this->dispatch('success', 'Init script saved successfully.');
} }
public function delete_init_script($script) public function delete_init_script($script)
@@ -101,7 +116,7 @@ class General extends Component
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray(); $this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
$this->database->save(); $this->database->save();
$this->refresh(); $this->refresh();
$this->emit('success', 'Init script deleted successfully.'); $this->dispatch('success', 'Init script deleted successfully.');
return; return;
} }
} }
@@ -119,7 +134,7 @@ class General extends Component
]); ]);
$found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename); $found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename);
if ($found) { if ($found) {
$this->emit('error', 'Filename already exists.'); $this->dispatch('error', 'Filename already exists.');
return; return;
} }
if (!isset($this->database->init_scripts)) { if (!isset($this->database->init_scripts)) {
@@ -133,7 +148,7 @@ class General extends Component
] ]
]); ]);
$this->database->save(); $this->database->save();
$this->emit('success', 'Init script added successfully.'); $this->dispatch('success', 'Init script added successfully.');
$this->new_content = ''; $this->new_content = '';
$this->new_filename = ''; $this->new_filename = '';
} }
@@ -146,7 +161,7 @@ class General extends Component
} }
$this->validate(); $this->validate();
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->dispatch('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database\Redis; namespace App\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
@@ -25,6 +25,7 @@ class General extends Component
'database.ports_mappings' => 'nullable', 'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'database.name' => 'Name', 'database.name' => 'Name',
@@ -43,6 +44,20 @@ class General extends Component
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
} }
} }
public function instantSaveAdvanced() {
try {
if (!$this->database->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->dispatch('success', 'Database updated successfully.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function submit() public function submit()
{ {
try { try {
@@ -51,7 +66,7 @@ class General extends Component
$this->database->redis_conf = null; $this->database->redis_conf = null;
} }
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->dispatch('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -60,23 +75,23 @@ class General extends Component
{ {
try { try {
if ($this->database->is_public && !$this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);
$this->db_url_public = null; $this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.'); $this->dispatch('success', 'Database is no longer publicly accessible.');
} }
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project\Database; namespace App\Livewire\Project\Database;
use Livewire\Component; use Livewire\Component;
@@ -38,7 +38,7 @@ class ScheduledBackups extends Component
public function delete($scheduled_backup_id): void public function delete($scheduled_backup_id): void
{ {
$this->database->scheduledBackups->find($scheduled_backup_id)->delete(); $this->database->scheduledBackups->find($scheduled_backup_id)->delete();
$this->emit('success', 'Scheduled backup deleted successfully.'); $this->dispatch('success', 'Scheduled backup deleted successfully.');
$this->refreshScheduledBackups(); $this->refreshScheduledBackups();
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project; namespace App\Livewire\Project;
use App\Models\Environment; use App\Models\Environment;
use Livewire\Component; use Livewire\Component;
@@ -23,8 +23,8 @@ class DeleteEnvironment extends Component
$environment = Environment::findOrFail($this->environment_id); $environment = Environment::findOrFail($this->environment_id);
if ($environment->isEmpty()) { if ($environment->isEmpty()) {
$environment->delete(); $environment->delete();
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]); return $this->redirectRoute('project.show', ['project_uuid' => $this->parameters['project_uuid']], navigate: true);
} }
return $this->emit('error', 'Environment has defined resources, please delete them first.'); return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Livewire\Project; namespace App\Livewire\Project;
use App\Models\Project; use App\Models\Project;
use Livewire\Component; use Livewire\Component;
@@ -22,9 +22,9 @@ class DeleteProject extends Component
]); ]);
$project = Project::findOrFail($this->project_id); $project = Project::findOrFail($this->project_id);
if ($project->applications->count() > 0) { if ($project->applications->count() > 0) {
return $this->emit('error', 'Project has resources defined, please delete them first.'); return $this->dispatch('error', 'Project has resources defined, please delete them first.');
} }
$project->delete(); $project->delete();
return redirect()->route('projects'); return $this->redirectRoute('projects', navigate: true);
} }
} }

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