Compare commits

...

660 Commits

Author SHA1 Message Date
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
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
Andras Bacsai
3f3a1283df Merge pull request #1452 from coollabsio/next
v4.0.0-beta.133
2023-11-14 15:07:15 +01:00
Andras Bacsai
087bfcad08 Update server model and version configurations 2023-11-14 15:06:03 +01:00
Andras Bacsai
efd2899ae3 Merge pull request #1450 from coollabsio/next
v4.0.0-beta.132
2023-11-14 14:22:17 +01:00
Andras Bacsai
e4b2195932 Fix manual Git webhook generation 2023-11-14 14:14:21 +01:00
Andras Bacsai
0590ed7b2e Update webhooks configuration and application search. 2023-11-14 14:07:48 +01:00
Andras Bacsai
3a3c9448a4 Add gitWebhook method to Application model and fix
Dockerfile input display
2023-11-14 14:07:42 +01:00
Andras Bacsai
36d65ad5a8 Fix Dockerfile location in deployment job 2023-11-14 14:07:33 +01:00
Andras Bacsai
8db66952e8 Add manual Git webhooks and migration files 2023-11-14 13:26:14 +01:00
Andras Bacsai
45fa88ca4d Add error handling for missing email settings in
EmailChannel.php
2023-11-14 11:04:45 +01:00
Andras Bacsai
84b74f0b57 Update version numbers to 4.0.0-beta.132 2023-11-14 10:59:02 +01:00
Andras Bacsai
423cf62d92 Add support for dynamic docker-compose file name
in ApplicationDeploymentJob.php
2023-11-14 08:52:17 +01:00
Andras Bacsai
c4d9deabef Add debugging statement to report exceptions in
development environment
2023-11-13 21:17:17 +01:00
Andras Bacsai
776b1cb68d Add unauthenticated method to handle
authentication exceptions
2023-11-13 21:16:48 +01:00
Andras Bacsai
fc3025398e Merge pull request #1447 from coollabsio/next
v4.0.0-beta.131
2023-11-13 19:35:43 +01:00
Andras Bacsai
457c16c4dc remove ray 2023-11-13 19:26:11 +01:00
Andras Bacsai
ccf63c67e8 fix: mariadb backups 2023-11-13 19:25:18 +01:00
Andras Bacsai
945157b30c Merge pull request #1446 from coollabsio/next
v4.0.0-beta.130
2023-11-13 17:09:10 +01:00
Andras Bacsai
13798392be fix: generate service fields 2023-11-13 17:06:43 +01:00
Andras Bacsai
0d05b0a3d6 Merge pull request #1445 from coollabsio/next
v4.0.0-beta.129
2023-11-13 16:48:18 +01:00
Andras Bacsai
e0d2f88d99 fix: fqdn for minio 2023-11-13 16:45:54 +01:00
Andras Bacsai
e260bfae02 Merge pull request #1443 from coollabsio/next
v4.0.0-beta.128
2023-11-13 15:49:51 +01:00
Andras Bacsai
5abd4a6d78 Update version and fix MINIO_BROWSER_REDIRECT_URL
and MINIO_SERVER_URL
2023-11-13 15:49:23 +01:00
Andras Bacsai
9dff1e5631 Merge pull request #1442 from coollabsio/next
v4.0.0-beta.127
2023-11-13 15:42:28 +01:00
Andras Bacsai
02332ade1b Fix URLs and remove unnecessary command in
ApplicationDeploymentJob.php
2023-11-13 15:41:49 +01:00
Andras Bacsai
486de58d5b Update database start commands 2023-11-13 15:27:33 +01:00
Andras Bacsai
606aeb2b61 Merge pull request #1441 from coollabsio/next
v4.0.0-beta.126
2023-11-13 15:21:45 +01:00
Andras Bacsai
3fc264560c Update dependencies and fix minor bugs. 2023-11-13 15:19:49 +01:00
Andras Bacsai
3dd9182281 Add sponsorship notification and disable option,
update dependencies
2023-11-13 14:44:54 +01:00
Andras Bacsai
c838ff7198 Update version numbers to 4.0.0-beta.126 2023-11-13 13:38:50 +01:00
Andras Bacsai
ca6db9c1a9 Merge pull request #1440 from coollabsio/next
v4.0.0-beta.125
2023-11-13 13:21:00 +01:00
Andras Bacsai
f27e00e80e Update version.json to include v4.0.0-beta.125 2023-11-13 13:20:28 +01:00
Andras Bacsai
60cf296f31 Update preview application deployment labels and version 2023-11-13 13:20:12 +01:00
Andras Bacsai
ea64e9d5ad Merge pull request #1439 from coollabsio/next
v4.0.0-beta.124
2023-11-13 13:03:46 +01:00
Andras Bacsai
55846c5635 Fix service retrieval and add error handling 2023-11-13 12:59:59 +01:00
Andras Bacsai
7763594e6e Add pull_latest_image function and update
build_image function to use it. Also add check for
dockerfile existence in start_by_compose_file
function.
2023-11-13 12:30:25 +01:00
Andras Bacsai
6b5339c1c1 Remove ray debug statement and refactor random
name generator
2023-11-13 11:44:13 +01:00
Andras Bacsai
f2980738e4 Fix documentation link in service-templates.json 2023-11-13 11:30:20 +01:00
Andras Bacsai
f0e3ad0461 Merge pull request #1432 from AlejandroAkbal/main
fix(fider template): use the correct docs url
2023-11-13 11:29:39 +01:00
Andras Bacsai
187050e098 Merge pull request #1435 from AshikNesin/main
Fix typo in onboarding page
2023-11-13 11:29:02 +01:00
Andras Bacsai
9e7823795d Fix null check for MINIO_BROWSER_REDIRECT_URL and
MINIO_SERVER_URL in generateServiceSpecificFqdns
function
2023-11-13 11:17:49 +01:00
Andras Bacsai
239459dfa8 Remove commented out code for minio service 2023-11-13 11:13:16 +01:00
Andras Bacsai
ce0f560c44 Add service-specific configuration fields and save
them to the database
2023-11-13 11:09:21 +01:00
Andras Bacsai
95baec99dd Fix typo in General.php component 2023-11-13 09:04:19 +01:00
Andras Bacsai
363e8fc0b5 Update code with bug fixes and improvements 2023-11-13 08:46:43 +01:00
Andras Bacsai
e49caba920 Add STRIPE_EXCLUDED_PLANS to services in
docker-compose.prod.yml
2023-11-13 08:46:17 +01:00
Ashik Nesin
30db2b2a09 Update typo in onboarding screen 2023-11-12 19:30:20 +00:00
Andras Bacsai
285666e181 Merge pull request #1434 from coollabsio/next
v4.0.0-beta.123
2023-11-12 19:11:31 +01:00
Andras Bacsai
003934ee1d disable service confs for now 2023-11-12 19:10:54 +01:00
Andras Bacsai
44c7958aa6 make fqdn super long 2023-11-12 19:09:38 +01:00
Alejandro Akbal
35b1a81dfe fix(fider template): use the correct docs url 2023-11-12 12:10:53 +00:00
Andras Bacsai
e40f397cc7 fix: service updates 2023-11-11 21:32:41 +01:00
Andras Bacsai
9fd8cd7e6c Merge pull request #1430 from coollabsio/next
v4.0.0-beta.122
2023-11-11 10:19:28 +01:00
Andras Bacsai
a94b7ee611 fix: container status jobs for old pr deployments 2023-11-11 10:18:40 +01:00
Andras Bacsai
fc68bf50b5 save 2023-11-10 22:04:04 +01:00
Andras Bacsai
0f99ee787c Merge pull request #1429 from coollabsio/next
v4.0.0-beta.121
2023-11-10 21:30:49 +01:00
Andras Bacsai
95777e978e fix: revert workdir to basedir 2023-11-10 21:02:39 +01:00
Andras Bacsai
fb0b9dbfed Add subscription exclusion for certain plans in
webhook handling
2023-11-10 15:41:44 +01:00
Andras Bacsai
9617000daa Add stripe_excluded_plans config variable and
handle excluded plans in webhook
2023-11-10 15:36:02 +01:00
Andras Bacsai
1818404172 Refactor application configuration blade file to
conditionally display tabs based on build pack
2023-11-10 13:46:14 +01:00
Andras Bacsai
d9a966fd98 Fix broken link to framework specific docs in
general.blade.php
2023-11-10 13:42:17 +01:00
Andras Bacsai
763ce5fc14 Update version numbers and deployment logs styling 2023-11-10 13:38:29 +01:00
Andras Bacsai
df021760a7 Merge pull request #1423 from coollabsio/next
v4.0.0-beta.120
2023-11-10 12:06:55 +01:00
Andras Bacsai
fb2598f2e4 Update UI elements and add new build pack option (static) 2023-11-10 11:33:15 +01:00
Andras Bacsai
7af07b2718 Add logging to DockerCleanupJob 2023-11-10 10:55:23 +01:00
Andras Bacsai
23a94c9378 Refactor DockerCleanupJob and Application model 2023-11-10 10:34:28 +01:00
Andras Bacsai
ed34fc9645 Update defaultClass in Select component 2023-11-10 10:14:46 +01:00
Andras Bacsai
cafd9e0ab2 Convert cpus limits to integer in database and
application classes
2023-11-10 09:54:40 +01:00
Andras Bacsai
e882477e21 Refactor navbar and add help us link 2023-11-10 09:49:47 +01:00
Andras Bacsai
db0e3cfcc4 fix: database proxy for services
version++
tiny css modifications
2023-11-10 09:41:42 +01:00
Andras Bacsai
b3c4429028 Merge pull request #1422 from coollabsio/next
v4.0.0-beta.119
2023-11-09 15:10:56 +01:00
Andras Bacsai
87ab4bd71e fix: local ip address 2023-11-09 15:05:42 +01:00
Andras Bacsai
61e1fdede9 feat: make service databases public 2023-11-09 14:59:38 +01:00
Andras Bacsai
b189919f97 Merge pull request #1421 from coollabsio/next
v4.0.0-beta.118
2023-11-09 12:47:05 +01:00
Andras Bacsai
8f5b084931 Refactor environment variable saving logic. 2023-11-09 12:40:53 +01:00
Andras Bacsai
eb96a5ae7b Update user authentication logic to use bcrypt
hashing algorithm
2023-11-09 12:29:03 +01:00
Andras Bacsai
f0fb9dbb94 Update Sentry and version configs to
4.0.0-beta.118
2023-11-09 12:19:08 +01:00
Andras Bacsai
cb2d4b4a0a Merge pull request #1420 from coollabsio/next
v4.0.0-beta.117
2023-11-09 11:59:01 +01:00
Andras Bacsai
3aace2d4f9 Update email recipient in SendEmail.php 2023-11-09 11:58:12 +01:00
Andras Bacsai
8c2ed75653 Update Docker images and add Directus service with
PostgreSQL.
2023-11-09 11:52:51 +01:00
Andras Bacsai
8c8aafbc65 Update version and fix directory path in
deployment job
2023-11-09 11:33:37 +01:00
Andras Bacsai
a2c39fd07e Merge pull request #1416 from coollabsio/next
v4.0.0-beta.116
2023-11-08 15:42:03 +01:00
Andras Bacsai
9698a051d9 Refactored code for better container management 2023-11-08 15:40:06 +01:00
Andras Bacsai
51423394ba Add deployment logs button to Telegram
notification
2023-11-08 14:37:01 +01:00
Andras Bacsai
8e5e36dd5b Update version numbers to 4.0.0-beta.116 and change docs link 2023-11-08 12:54:13 +01:00
Andras Bacsai
c1dd05dcd8 Merge pull request #1413 from coollabsio/next
Refactored database backup job to handle missing
2023-11-08 12:46:14 +01:00
Andras Bacsai
dd1ce6ee6c Refactor database backup job to simplify code 2023-11-08 12:45:48 +01:00
Andras Bacsai
fe4c6d396c Refactored database backup job to handle missing
POSTGRES_DB environment variable
2023-11-08 12:45:31 +01:00
Andras Bacsai
8db54ec069 Merge pull request #1412 from coollabsio/next
Fix database type check in service show blade file
2023-11-08 12:42:35 +01:00
Andras Bacsai
3abc720926 Fix database type check in service show blade file 2023-11-08 12:42:20 +01:00
Andras Bacsai
64cc0b63f1 Merge pull request #1411 from coollabsio/next
v4.0.0-beta.115
2023-11-08 12:41:25 +01:00
Andras Bacsai
c78068466b Add custom PostgreSQL configuration to
StandalonePostgresql
2023-11-08 12:40:05 +01:00
Andras Bacsai
88e407756d Update version numbers and database URLs 2023-11-08 12:26:57 +01:00
Andras Bacsai
1538116e6e Merge pull request #1410 from coollabsio/next
v4.0.0-beta.114
2023-11-08 11:31:40 +01:00
Andras Bacsai
aba47d58a4 Add customRepository property to
ApplicationDeploymentJob class
Fix weird image names in case of custom git
2023-11-08 11:30:54 +01:00
Andras Bacsai
e7f184dd82 Add conditional check for backups tab in service
show view
2023-11-08 11:07:44 +01:00
Andras Bacsai
2ad8d7812b Refactor database backup job to improve code
readability and maintainability.
2023-11-08 11:05:57 +01:00
Andras Bacsai
8212bb99a1 Update database backup job and version number 2023-11-08 10:47:39 +01:00
Andras Bacsai
5fc382d09d Merge pull request #1406 from coollabsio/next
v4.0.0-beta.113
2023-11-08 10:28:57 +01:00
Andras Bacsai
78a80c46da Add nixpacks environment variables to deployment
job
2023-11-08 10:13:20 +01:00
Andras Bacsai
c365d132af Fix empty public port in database configuration 2023-11-08 09:30:38 +01:00
Andras Bacsai
4dc3db3845 Update versions and fix database replication (init values are changeable) in
CloneProject.php
2023-11-08 09:07:30 +01:00
Andras Bacsai
b9427d2ec1 Merge pull request #1398 from coollabsio/next
v4.0.0-beta.112
2023-11-07 15:01:56 +01:00
Andras Bacsai
332a0b9e04 Remove ANSI colors from console output. 2023-11-07 14:40:58 +01:00
Andras Bacsai
18e98aaf52 Add S3 storage to Livewire components and fix
backup job network issue
2023-11-07 14:09:24 +01:00
Andras Bacsai
a7f9fad627 Add support for Dockerfile target build 2023-11-07 13:49:15 +01:00
Andras Bacsai
b01f6ac414 Fix docker network connection in StartService.php 2023-11-07 13:29:05 +01:00
Andras Bacsai
e1bc2cc406 Fix docker network connection issue in
StartService.php
2023-11-07 13:28:48 +01:00
Andras Bacsai
74830b12f3 Fix Docker network creation command in
StartService.php
2023-11-07 13:28:10 +01:00
Andras Bacsai
56a977c676 update n8n 2023-11-07 12:50:18 +01:00
Andras Bacsai
a0bb5733e6 lol n8n with umami db name 2023-11-07 12:30:37 +01:00
Andras Bacsai
516e10ddf2 feat: service database backups 2023-11-07 12:11:47 +01:00
Andras Bacsai
2976c72e09 fix: ui 2023-11-07 10:18:28 +01:00
Andras Bacsai
7377e9e415 fix: dockercleanupjob should be released back 2023-11-07 09:51:48 +01:00
Andras Bacsai
d77c55148b fix: github source view 2023-11-07 09:47:25 +01:00
Andras Bacsai
ad7aa2eed6 fix: github source view 2023-11-07 09:44:47 +01:00
Andras Bacsai
5cec50efbe update install script 2023-11-06 21:14:32 +01:00
Andras Bacsai
ef8686d4da Merge pull request #1383 from krsilas/fix/check-docker-installation
Check if docker installation was successful
2023-11-06 21:13:29 +01:00
Andras Bacsai
581cc73cd4 Merge pull request #1396 from coollabsio/next
v4.0.0-beta.111
2023-11-06 21:08:16 +01:00
Andras Bacsai
358fbf6b3d cleanup not forced 2023-11-06 21:08:02 +01:00
Andras Bacsai
ca0535c285 update cleanup 2023-11-06 20:58:03 +01:00
Andras Bacsai
9007a645a6 fix: build_image not found 2023-11-06 20:53:51 +01:00
Andras Bacsai
68b1b9774d Merge pull request #1385 from theh2so4/next
[+] Templates: NextCloud and Gitea
2023-11-06 19:13:51 +01:00
Andras Bacsai
b9b4c23d5b update init 2023-11-06 18:15:23 +01:00
Andras Bacsai
149fee2452 fix: deletions 2023-11-06 18:04:18 +01:00
Andras Bacsai
87af9e46a6 fix:ui 2023-11-06 17:27:22 +01:00
Andras Bacsai
d6f87d3fb6 fix: ui for labels 2023-11-06 17:25:54 +01:00
Andras Bacsai
493af61233 fix 2023-11-06 15:51:27 +01:00
Andras Bacsai
ab03908f1d updates 2023-11-06 15:48:15 +01:00
Andras Bacsai
9ef7cf3c12 update service templates 2023-11-06 15:43:56 +01:00
Andras Bacsai
eab7fd44d4 fix: service dockercompose predefined networks
version++
fix: modal of changing service stack
fix: appwrite template
2023-11-06 15:22:11 +01:00
Andras Bacsai
0d1d25a945 Merge pull request #1393 from coollabsio/next
v4.0.0-beta.110
2023-11-06 14:13:38 +01:00
Andras Bacsai
534372c29c fix: env variables
fix: revert custom network for a bit
2023-11-06 14:12:22 +01:00
Andras Bacsai
1ccb239797 version++ 2023-11-06 13:54:00 +01:00
Andras Bacsai
66287b43d0 fix: container logs are now followable in full-screen and sorted by timestamp 2023-11-06 13:53:05 +01:00
Andras Bacsai
143e4e0d23 lol 2023-11-06 13:30:37 +01:00
Andras Bacsai
73f3a09157 oops 2023-11-06 13:29:44 +01:00
Andras Bacsai
5ce449aa08 Merge pull request #1381 from coollabsio/next
v4.0.0-beta.109
2023-11-06 13:16:55 +01:00
Andras Bacsai
6203804713 handle 2023-11-06 13:07:29 +01:00
Andras Bacsai
0858faf628 fix: remove filter 2023-11-06 12:53:43 +01:00
Andras Bacsai
a84f3e0577 fix link 2023-11-06 12:46:58 +01:00
Andras Bacsai
8d571a5eab fix: add nixpacks info 2023-11-06 12:40:53 +01:00
Andras Bacsai
7a117c61c4 fix: separate delete with validation of server 2023-11-06 12:31:02 +01:00
Andras Bacsai
8b034f15fc fix: delete resource if server is not functional
fix: set status to exited on all resources
2023-11-06 11:51:20 +01:00
Andras Bacsai
b4a6499c83 fix: port number should be int 2023-11-06 10:58:00 +01:00
Andras Bacsai
c083acaeef fix: resourcesdelete command 2023-11-06 10:55:46 +01:00
Andras Bacsai
9c6d8320d8 fix: UI 2023-11-06 10:54:11 +01:00
Andras Bacsai
0e7a304610 fix: private key not found error 2023-11-06 10:53:01 +01:00
Andras Bacsai
83993cbbb2 fix: telegram text 2023-11-06 10:49:35 +01:00
Andras Bacsai
6840ddd3e6 fix: no environments 2023-11-06 10:48:30 +01:00
Andras Bacsai
2c6ece62bb fixes 2023-11-06 10:45:06 +01:00
Andras Bacsai
3f8514050e fix: set default from/sender names 2023-11-06 10:26:56 +01:00
Andras Bacsai
a4a653603e fix: missing $mailMessage 2023-11-06 10:23:51 +01:00
Andras Bacsai
b6d8851c99 fix: no id found 2023-11-06 10:22:46 +01:00
Andras Bacsai
bcd7697f50 fix: delete destination 2023-11-06 10:20:13 +01:00
Andras Bacsai
f1da735c40 fix: gh webhook response 200 to installation_repositories 2023-11-06 10:16:21 +01:00
Andras Bacsai
01331c287b fix: notification url in containerstatusjob 2023-11-06 10:10:40 +01:00
Andras Bacsai
3320de787a fix: network service parse 2023-11-06 09:55:22 +01:00
Andras Bacsai
2bddb09384 fix: set labels on generate domain 2023-11-06 09:27:00 +01:00
Andras Bacsai
6f673d7a07 fixes 2023-11-05 09:49:23 +01:00
Andras Bacsai
0a5a101ef4 update github actions 2023-11-03 18:01:17 +01:00
Andras Bacsai
88590fbf0f fix: dockerfile build pack fix 2023-11-03 17:55:53 +01:00
Andras Bacsai
90291b2edf fix: deployments ui 2023-11-03 17:45:30 +01:00
Andras Bacsai
070573f0df fix: local dev repo 2023-11-03 15:11:06 +01:00
Andras Bacsai
e583beb753 fix: invoice.paid should sleep for 5 seconds 2023-11-03 14:51:29 +01:00
Andras Bacsai
d31683df61 update 2023-11-03 14:39:11 +01:00
Andras Bacsai
0a83ed82fa tinkerwell 2023-11-03 14:38:34 +01:00
TheH2SO4
4031e477ee [+] Template: Gitea (PostgreSQL) 2023-11-03 13:55:14 +01:00
TheH2SO4
0c1991d1de [+] Template: Gitea MariaDB + (fix) 2023-11-03 13:40:36 +01:00
TheH2SO4
05b697b18c [+] Template: MySQL + (Fix) 2023-11-03 13:40:07 +01:00
TheH2SO4
061aeba605 [+] Template: Gitea (MariaDB) 2023-11-03 13:38:50 +01:00
TheH2SO4
f446e784cc [+] Template: Gitea (MySQL) 2023-11-03 12:21:15 +01:00
TheH2SO4
72fe24d98e [+] Template: Gitea 2023-11-03 12:04:01 +01:00
Andras Bacsai
0cd3a3d848 fix: increase polling time for services
fix: allow domain as ip address
2023-11-03 10:57:58 +01:00
TheH2SO4
126b2dc65b [+] Template: NextCloud
🆕 **New Template**:

-> ℹ️ **NextCloud**: NextCloud is a self-hosted, open-source platform that provides file storage, collaboration, and communication tools for seamless data management.
2023-11-03 08:34:24 +01:00
Andras Bacsai
a0031efce0 resale license check needs to be updated 2023-11-02 14:10:29 +01:00
Andras Bacsai
3bffe3f010 fix: missing environment variables prevewi on service 2023-11-02 14:03:02 +01:00
Andras Bacsai
b9a37233a2 disable license check for now 2023-11-02 11:45:43 +01:00
Silas Krause
8ae18f49dc Add missing fi 2023-11-01 22:13:25 +01:00
Silas Krause
4feb99cbe0 Check if docker installation was successful 2023-11-01 21:52:08 +01:00
Andras Bacsai
aab122d97e add cache-key nixpacks 2023-11-01 21:05:24 +01:00
Andras Bacsai
658d608f55 ok, it is not nixpacks problem 2023-11-01 21:02:05 +01:00
Andras Bacsai
0838343841 fix: pull requests
feat: add follow for full screen logs
2023-11-01 20:55:21 +01:00
Andras Bacsai
b557ea1e1d revert nixpacks version 2023-11-01 20:54:50 +01:00
Andras Bacsai
4520070df3 fix: pull requests deployments
feat: filter deployments logs by pull requests
2023-11-01 15:39:47 +01:00
Andras Bacsai
be8ea78b1b feat: deployment logs fullscreen 2023-11-01 14:06:15 +01:00
Andras Bacsai
1175d68ab5 feat: full screen logs
fix: logs are in order now
2023-11-01 13:47:40 +01:00
Andras Bacsai
f56d373ed2 update nixpacks 2023-11-01 12:54:49 +01:00
Andras Bacsai
c6253658ca feat: restart application
fix: a few things in application deployment job
2023-11-01 12:19:08 +01:00
Andras Bacsai
4249aec936 Merge branch 'main' into next 2023-11-01 10:56:03 +01:00
Andras Bacsai
25f80aba5f Merge pull request #1376 from mauvehed/fix/basedir-permissions 2023-10-31 09:18:22 +01:00
Andras Bacsai
4550983761 Update install.sh 2023-10-31 08:13:50 +01:00
Andras Bacsai
b0238372a2 Update install.sh
Do not change permission on /data
2023-10-31 08:13:06 +01:00
mauvehed
a021b71496 fix(install.sh): change ownership and permissions only for /data/coolify directory instead of /data
The ownership and permissions are now set only for the /data/coolify directory instead of the entire /data directory. This ensures that the ownership and permissions are applied only to the necessary directory and not to other directories within /data.
2023-10-29 10:05:16 -05:00
Andras Bacsai
e3958d9626 added a few services 2023-10-27 14:22:35 +02:00
Andras Bacsai
55891d7001 Merge pull request #1367 from itishermann/main
[+] Template: Kuzzle, Moodle, Sonarqube, RabbitMQ
2023-10-27 13:16:52 +02:00
Andras Bacsai
b12ac8bb29 Merge pull request #1364 from theh2so4/main
[+] Template: BudgE, Duplicati, Jellyfin, phpMyAdmin, Vaultwarden, Whoogle and FileBrowser
2023-10-27 12:57:00 +02:00
TheH2SO4
728a9f88eb [!] Template: FileBrowser 2023-10-27 12:23:16 +02:00
TheH2SO4
57267c3ee0 [+] Template: FileBrowser
🆕 **New Template**:

-> ℹ️ **FileBrowser**: FileBrowser simplifies file and folder management on various storage systems.
2023-10-27 12:21:14 +02:00
Andras Bacsai
d3d133ed1f version++ 2023-10-27 12:07:48 +02:00
Andras Bacsai
f5240abbe5 Merge pull request #1371 from coollabsio/next
v4.0.0-beta.108
2023-10-27 11:44:35 +02:00
Andras Bacsai
abf5840f97 fixing 2023-10-27 11:44:10 +02:00
Andras Bacsai
dc6d5af4aa Merge pull request #1370 from coollabsio/next
v4.0.0-beta.107
2023-10-27 11:24:03 +02:00
Andras Bacsai
0b88cd69f2 fix: remove coolify labels from ui 2023-10-27 11:23:29 +02:00
Andras Bacsai
ce165719d6 Merge pull request #1369 from coollabsio/next
v4.0.0-beta.106
2023-10-27 10:43:54 +02:00
Andras Bacsai
4f543ce20f remove ray 2023-10-27 10:43:05 +02:00
Andras Bacsai
55f957df21 fix: git ls-remote 2023-10-27 10:42:56 +02:00
Andras Bacsai
38f59b9410 revert 2023-10-27 10:30:15 +02:00
Andras Bacsai
ebe6655349 update invoice paid 2023-10-27 10:28:43 +02:00
Andras Bacsai
038ea08ca7 add payment_intent.payment_failed to subs 2023-10-27 10:26:35 +02:00
Andras Bacsai
ba424efd39 cloud: fix subs 2023-10-27 10:17:13 +02:00
Andras Bacsai
75aef0e60b Merge pull request #1366 from coollabsio/next
v4.0.0-beta.105
2023-10-27 09:31:06 +02:00
Andras Bacsai
eda8b34297 fix 2023-10-27 09:28:43 +02:00
Andras Bacsai
d8151ddb2e fix: add ssh options to git ls-remote 2023-10-27 09:25:15 +02:00
Hermann Kao
7925228f97 add service template for sonarqube 2023-10-27 01:41:07 +02:00
Hermann Kao
a7dc62aaa0 add service template for rabbitmq 2023-10-27 00:42:15 +02:00
Hermann Kao
632dbd155b add service template for moodle based on bitnami images 2023-10-27 00:18:06 +02:00
Hermann Kao
fe092bb7a5 add service template for kuzzle 2023-10-26 23:29:59 +02:00
Andras Bacsai
928345c8ea fix: force password reset on invited accounts 2023-10-26 20:45:38 +02:00
Andras Bacsai
52d6fb51d5 pocketbase 2023-10-26 15:53:42 +02:00
Andras Bacsai
06d7c69487 add nocodb 2023-10-26 13:32:23 +02:00
Andras Bacsai
756c7f81ca fix: if user is invited, that means its email is verified 2023-10-26 13:00:40 +02:00
Andras Bacsai
d7af57a95e fix: custom labels only should have non-coolify labels
fix: pull helper image every 10 minutes instead of every deployment
2023-10-26 11:38:37 +02:00
TheH2SO4
722ff15fbd [+] Template: Vaultwarden
🆕 **New Template**:

-> ℹ️ **Vaultwarden**: Vaultwarden is an open-source password manager that allows you to securely store and manage your passwords, helping you stay organized and protected.
2023-10-26 11:21:31 +02:00
Andras Bacsai
f9c469497e version++ 2023-10-26 11:15:37 +02:00
Andras Bacsai
7ecbedb48a Merge pull request #1365 from coollabsio/next
v4.0.0-beta.104
2023-10-26 11:11:59 +02:00
Andras Bacsai
76878f66b9 remove ray 2023-10-26 11:07:25 +02:00
Andras Bacsai
b9afef50c4 version++ 2023-10-26 10:35:14 +02:00
Andras Bacsai
83ebd1e649 feat: improve deployment time by a lot 2023-10-26 10:33:57 +02:00
Andras Bacsai
76431c3fd5 service updates 2023-10-26 10:02:51 +02:00
Andras Bacsai
96a4d0bbb0 fix: lock SERVICE_FQDN envs 2023-10-26 10:02:45 +02:00
Andras Bacsai
4cfc739730 add openblocks 2023-10-26 09:34:02 +02:00
TheH2SO4
fcd0d8d359 [+] Template: BudgE
🆕 **New Template**:

-> ℹ️ **BudgE**: Budge is an open-source 'budgeting with envelopes' personal finance app, helping you manage your finances effectively.
2023-10-25 22:22:20 +02:00
TheH2SO4
3fcac0ac35 [+] Template: phpMyAdmin
🆕 **New Template**:

-> ℹ️ **phpMyAdmin**: phpMyAdmin is a web-based database management tool for administering your MySQL and MariaDB databases through a user-friendly interface.
2023-10-25 21:57:03 +02:00
TheH2SO4
fcc8a7f0ed [!] Template Fix: Whoogle
🐛 **Bug Fix**:

ℹ️ **Whoogle**: Tags section was not added, it's now fixed.
2023-10-25 21:50:55 +02:00
TheH2SO4
6950ead041 [+] Template: Jellyfin
🆕 **New Template**:

-> ℹ️ **Jellyfin**: Jellyfin is an open-source media server for hosting and streaming your media collection, providing an alternative to proprietary media platforms.
2023-10-25 21:46:52 +02:00
TheH2SO4
f78c49fc82 [+] Template: Whoogle
🆕 **New Template**:

-> ℹ️ **Whoogle**: Whoogle is a self-hosted, privacy-focused search engine front-end for accessing Google search results without tracking and data collection.
2023-10-25 21:40:38 +02:00
TheH2SO4
5f2581020b [+] Template: Duplicati
🆕 **New Template**:

-> ℹ️ **Duplicati**: Duplicati is an open-source backup solution, allowing you to safeguard your data with ease through scheduled backups and encryption.
2023-10-25 21:36:57 +02:00
TheH2SO4
2fb674ae85 Merge pull request #4 from coollabsio/main
Update
2023-10-25 21:27:42 +02:00
Andras Bacsai
a95bd906bc Merge pull request #1363 from coollabsio/next
v4.0.0-beta.103
2023-10-25 20:21:27 +02:00
Andras Bacsai
21795cf788 fix: space in build args 2023-10-25 20:19:38 +02:00
Andras Bacsai
6e98fd9403 grafana + openblocks 2023-10-25 20:13:45 +02:00
Andras Bacsai
ead1edc2b9 Services 2023-10-25 15:44:34 +02:00
Andras Bacsai
db822cb876 Merge pull request #1357 from theh2so4/main
[+] Templates: Dashboard, Emby, EmbyStat and Grocy
2023-10-25 15:41:54 +02:00
Andras Bacsai
65bfce43c0 fix: server settings guarded 2023-10-25 11:50:22 +02:00
Andras Bacsai
50fc05ab52 update init script 2023-10-25 11:43:18 +02:00
Andras Bacsai
c9cf5c486f Merge pull request #1362 from coollabsio/next
v4.0.0-beta.102
2023-10-25 11:07:17 +02:00
Andras Bacsai
379f4b9dff feat: show webhook on ui
feat: n8n service
2023-10-25 10:43:07 +02:00
Andras Bacsai
aa02b8d433 fix: rate limit for api + add mariadb + mysql 2023-10-25 09:56:58 +02:00
Andras Bacsai
70ecb92e82 cleanup ssh dir on start 2023-10-25 09:41:41 +02:00
Andras Bacsai
d5cc2a2eed feat: download local backups 2023-10-25 09:28:26 +02:00
Andras Bacsai
2b91bd24c5 Merge pull request #1361 from coollabsio/next
v4.0.0-beta.101
2023-10-24 15:51:54 +02:00
Andras Bacsai
5e8ac1b48e fix: mongodb healtcheck command 2023-10-24 15:47:29 +02:00
Andras Bacsai
dc86170ef5 version++ 2023-10-24 15:41:44 +02:00
Andras Bacsai
0232cf5b4c feat: lock environment variables 2023-10-24 15:41:21 +02:00
Andras Bacsai
6e73f7f2e4 fix: encrypt mongodb password 2023-10-24 15:40:29 +02:00
Andras Bacsai
61c43804e3 Merge pull request #1360 from coollabsio/next
v4.0.0-beta.100
2023-10-24 14:44:31 +02:00
Andras Bacsai
72421d692b add slogans 2023-10-24 14:36:43 +02:00
Andras Bacsai
f801bb98cd feat: mysql, mariadb 2023-10-24 14:31:28 +02:00
Andras Bacsai
b2d111e49a feat: simple search functionality 2023-10-24 12:33:49 +02:00
Andras Bacsai
c82e02218f version++ 2023-10-24 11:08:59 +02:00
Andras Bacsai
29f64076de fix: syncbunny command 2023-10-24 11:08:15 +02:00
Andras Bacsai
393c334b12 version++ 2023-10-24 11:08:11 +02:00
Andras Bacsai
678b264688 fix: make sure coolfiy network exists on install 2023-10-24 11:08:05 +02:00
Andras Bacsai
2620bfbf08 Merge pull request #1359 from coollabsio/next
v4.0.0-beta.99
2023-10-24 10:59:36 +02:00
Andras Bacsai
18c32decad guarded 2023-10-24 10:43:34 +02:00
Andras Bacsai
a6f9e5f0af fixes 2023-10-24 10:42:33 +02:00
Andras Bacsai
f187040b7e fix: mongodb backup 2023-10-24 10:42:28 +02:00
Andras Bacsai
5510321776 syncbunny update 2023-10-24 10:22:36 +02:00
Andras Bacsai
69691b2ca7 fix: service template generator + appwrite 2023-10-24 10:19:12 +02:00
Andras Bacsai
8bfc1a7c06 fix: do not allow to delete env if a resource is defined 2023-10-24 10:11:21 +02:00
Andras Bacsai
554222abc7 fix: cleanup stucked resources on start 2023-10-24 10:10:55 +02:00
Andras Bacsai
b1a1aeeb75 fix: clone to with the same environment name 2023-10-24 10:10:45 +02:00
Andras Bacsai
91acd4cb6a fix: backups should be done with internal db url
fix: create default database on mongodb start with a collection
2023-10-24 09:34:35 +02:00
TheH2SO4
6c5a1c317a [!] Mistake 2023-10-23 13:14:43 +02:00
TheH2SO4
b09a9f871e [+] Template: Fenrus
🆕 **New Template**:

-> ℹ️ **Fenrus**: A personal home page for quick access to all your personal apps/sites.
2023-10-23 13:12:50 +02:00
TheH2SO4
b5506f006b [+] Template: Dashboard
🆕 **New Template**:

-> ℹ️ **Dashboard**: A dashboard. Inspired by SUI, it offers simple customization through JSON-files and a handy search bar to help you browse the internet more efficiently.
2023-10-23 13:05:22 +02:00
TheH2SO4
a6c3594448 [+] Template: Grocy
🆕 **New Template**:

-> ℹ️ **Grocy**: Grocy is a self-hosted, web-based household management and grocery list application, designed to simplify your household chores and grocery shopping.
2023-10-23 12:48:13 +02:00
TheH2SO4
5dd3952230 [+] Template: EmbyStat
🆕 **New Template**:

-> ℹ️ **EmbyStat**: EmyStat is an open-source, self-hosted web analytics tool, designed to provide insight into website traffic and user behavior, of your local Emby deployement, all within your control.
2023-10-23 12:41:48 +02:00
TheH2SO4
22ec0f8826 [+] Template: Emby
🆕 **New Template**:

-> ℹ️ **Emby**: A media server software that allows you to organize, stream, and access your multimedia content effortlessly, making it easy to enjoy your favorite movies, TV shows, music, and more.
2023-10-23 11:30:01 +02:00
TheH2SO4
da6e04bb1a Merge pull request #3 from coollabsio/main
Update
2023-10-21 22:57:27 +02:00
Andras Bacsai
aaeacad781 Merge pull request #1350 from coollabsio/next
v4.0.0-beta.98
2023-10-20 18:17:50 +02:00
Andras Bacsai
b539f40fa5 fix 2023-10-20 18:16:47 +02:00
Andras Bacsai
fae340afcb fix: boarding 2023-10-20 18:15:25 +02:00
Andras Bacsai
69ebff1a7a Merge pull request #1347 from coollabsio/next
v4.0.0-beta.97
2023-10-20 15:25:50 +02:00
Andras Bacsai
5d9cfc393e add s3 to magicbar 2023-10-20 15:02:40 +02:00
Andras Bacsai
e2a256b31c add api tokens to magic bar 2023-10-20 15:00:57 +02:00
Andras Bacsai
4855af7e57 feat: start all kinds of things 2023-10-20 14:58:00 +02:00
Andras Bacsai
a664174c02 feat: api tokens + deploy webhook 2023-10-20 14:51:01 +02:00
Andras Bacsai
c19c13b4e2 feat: cloning project 2023-10-20 12:34:53 +02:00
Andras Bacsai
266b99bc25 fix: port exposes change, shoud regenerate label 2023-10-20 12:34:25 +02:00
Andras Bacsai
51ef24e1fb fix 2023-10-20 09:38:21 +02:00
Andras Bacsai
33d38ccf40 fix: preselect s3 storage if available 2023-10-20 09:38:13 +02:00
Andras Bacsai
f470ebbbe0 ui: updates 2023-10-20 09:29:09 +02:00
Andras Bacsai
11bd46b200 wip: mongodb backup 2023-10-19 17:17:38 +02:00
Andras Bacsai
53f5674771 wip: mongodb backup 2023-10-19 13:46:15 +02:00
Andras Bacsai
c53d88902c feat: standalone mongodb 2023-10-19 13:32:03 +02:00
Andras Bacsai
e342c4fd65 fix: add PGUSER to prevent HC warning 2023-10-19 11:58:12 +02:00
Andras Bacsai
aab7bd5e28 Merge pull request #1345 from altinselimi/patch-1
Fix spelling error in README.md
2023-10-19 11:49:42 +02:00
Andras Bacsai
3adefb9e49 command: generate services 2023-10-19 11:28:25 +02:00
TheH2SO4
1bfce6716c Merge pull request #2 from coollabsio/next
Next
2023-10-19 11:18:19 +02:00
Altin Selimi
c904441787 Update README.md
Fix image -> imagine spelling error
2023-10-19 11:02:56 +02:00
Andras Bacsai
b7f79ae034 Merge pull request #1333 from theh2so4/main
[+] Templates: BabyBuddy, Code-Server, Dokuwiki, Heimdall, MeTube, SnapDrop and PairDrop
2023-10-19 10:51:24 +02:00
Andras Bacsai
2d63fcdc7f implement new service templates 2023-10-19 10:51:03 +02:00
Andras Bacsai
c1d0cabcfb fix: service docs links 2023-10-19 10:50:52 +02:00
Andras Bacsai
166419b13a update contribution guide 2023-10-19 10:50:47 +02:00
Andras Bacsai
cfc4d3acc7 Merge branch 'main' into next 2023-10-19 09:23:30 +02:00
Andras Bacsai
13a0c2cf43 Merge pull request #1344 from liweiyi88/github-trending-badge
Add github trending badge in readme recognitions
2023-10-19 09:23:01 +02:00
liweiyi88
6ef6975432 add github trending badge in readme recognitions 2023-10-19 09:41:54 +11:00
Andras Bacsai
2c40e93d3b wip: PAT by team 2023-10-18 18:02:09 +02:00
Andras Bacsai
a30ae4fb38 version++ 2023-10-18 15:49:58 +02:00
Andras Bacsai
5b8785d1a9 update prod compose 2023-10-18 15:49:50 +02:00
Andras Bacsai
f6f3364269 Merge pull request #1343 from coollabsio/next
v4.0.0-beta.96
2023-10-18 15:43:26 +02:00
Andras Bacsai
2f93f4450f fix: containerStatus job 2023-10-18 15:43:14 +02:00
Andras Bacsai
2ad7c2b1ce fix: remove custom port from git repo url 2023-10-18 15:33:07 +02:00
Andras Bacsai
6c848199ed fix: add custom port as ssh option to deploy_key based commands 2023-10-18 15:23:43 +02:00
Andras Bacsai
76aab722b8 fix: limit horizon processes to 2 by default 2023-10-18 15:07:04 +02:00
Andras Bacsai
12290304c4 Merge pull request #1342 from coollabsio/next
v4.0.0-beta.95
2023-10-18 14:47:05 +02:00
Andras Bacsai
3a27d13c3e fix 2023-10-18 14:46:26 +02:00
Andras Bacsai
4f588ced96 call handle not matter what 2023-10-18 14:43:48 +02:00
Andras Bacsai
e266c7cdec fix: email channel no recepients 2023-10-18 14:22:09 +02:00
Andras Bacsai
eedc3faba3 fix: labels 2023-10-18 14:14:40 +02:00
Andras Bacsai
2e2c932f07 Merge pull request #1341 from coollabsio/next
v4.0.0-beta.94
2023-10-18 12:49:11 +02:00
Andras Bacsai
e4aed185a2 fix: label generation 2023-10-18 12:48:29 +02:00
Andras Bacsai
dddbe40bbe fix dashboard ui on small screens 2023-10-18 11:35:36 +02:00
Andras Bacsai
59d6818f70 Merge pull request #1339 from coollabsio/next
v4.0.0-beta.93
2023-10-18 11:30:40 +02:00
Andras Bacsai
7678cd47df fix: add config_hash if its null (old deployments) 2023-10-18 11:26:01 +02:00
Andras Bacsai
b101fbacd4 fix: do not show configuration changed if config_hash is null 2023-10-18 11:22:56 +02:00
Andras Bacsai
a61a86dc3b feat: show if config is not applied 2023-10-18 11:20:40 +02:00
Andras Bacsai
0b3cde44c3 feat: able to customize docker labels on applications 2023-10-18 10:32:08 +02:00
TheH2SO4
618d5d837c [+] Template: Dokuwiki
🆕 **New Templates**:

-> ℹ️ **Dokuwiki**: A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.
2023-10-18 10:01:37 +02:00
TheH2SO4
d234e8969d [+] Template: MeTube
🆕 **New Template**:

-> ℹ️ **MeTube**: A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.
2023-10-18 09:22:47 +02:00
TheH2SO4
1be77b3fea [+] Template: BabyBuddy
🆕 **New Template**:

-> ℹ️ **Heimdall**: Baby Buddy is an open-source web application that helps parents track their baby's daily activities, growth, and health with ease. It's a handy tool for new parents to keep a close eye on their little one's development.
2023-10-18 09:11:21 +02:00
Andras Bacsai
6b302ab786 Add restarting indicator to resources 2023-10-18 09:03:14 +02:00
TheH2SO4
5831dd6196 Merge pull request #1 from coollabsio/main
4.0.0-beta.92
2023-10-18 08:28:56 +02:00
Andras Bacsai
3c623f13e2 revert version 2023-10-17 20:54:54 +02:00
Andras Bacsai
da54c24e8d fix: setup:dev script & contribution guide 2023-10-17 20:54:26 +02:00
Andras Bacsai
1e39c3d5ab Merge pull request #1338 from coollabsio/next
v4.0.0-beta.92
2023-10-17 19:03:04 +02:00
Andras Bacsai
6071412986 fix: proxy start process 2023-10-17 19:00:23 +02:00
Andras Bacsai
ba7148206a Merge pull request #1336 from coollabsio/next
v4.0.0-beta.91
2023-10-17 15:41:30 +02:00
Andras Bacsai
59c5b22e6c fix: always start proxy if not NONE is selected 2023-10-17 15:40:47 +02:00
Andras Bacsai
be7f2ad9c4 ui: add helper to service domains 2023-10-17 15:34:20 +02:00
Andras Bacsai
62295ef573 Merge pull request #1335 from coollabsio/next
v4.0.0-beta.90
2023-10-17 14:45:26 +02:00
Andras Bacsai
ceb9fcf3b6 service: wordpress 2023-10-17 14:44:25 +02:00
Andras Bacsai
60282f7b6c fix: only include config.json if its exists and a file 2023-10-17 14:23:07 +02:00
Andras Bacsai
f14b0a3411 Merge pull request #1334 from coollabsio/next
v4.0.0-beta.89
2023-10-17 14:06:12 +02:00
Andras Bacsai
30af317bd9 fix: show docker build logs 2023-10-17 14:04:21 +02:00
Andras Bacsai
95faa1c3ad fix: noindex meta tag 2023-10-17 13:28:33 +02:00
TheH2SO4
423d31f227 Merge branch 'main' into main 2023-10-17 13:24:22 +02:00
TheH2SO4
fbb063030d [+] Templates: Heimdall, PairDrop and SnapDrop
🆕 **New Template**:

-> ℹ️ **Heimdall**: Heimdall is a self-hosted dashboard for managing and organizing your server applications, providing a centralized and efficient interface.
-> ℹ️ **PairDrop**: Pairdrop is a self-hosted file sharing and collaboration platform, offering secure file sharing and collaboration capabilities for efficient teamwork.
-> ℹ️ **SnapDrop**: A self-hosted file-sharing service for secure and convenient file transfers, whether on a local network or the internet.
2023-10-17 13:22:37 +02:00
TheH2SO4
1968726cfe [+] Template: Code-Server
🆕 **New Template**:

-> ℹ️ **Code-Server**: Code-Server is a self-hosted, web-based code editor that enables remote coding and collaboration from any device, anywhere.
2023-10-17 12:53:44 +02:00
Andras Bacsai
fb280afe41 Merge pull request #1332 from coollabsio/next
v4.0.0-beta.88
2023-10-17 12:41:45 +02:00
Andras Bacsai
fd488a561a feat: use docker login credentials from server 2023-10-17 12:35:04 +02:00
Andras Bacsai
ab57a5d8ef Merge pull request #1331 from coollabsio/next
v4.0.0-beta.87
2023-10-17 12:14:03 +02:00
Andras Bacsai
f5ae222a6e fix: add internal domain names during build process 2023-10-17 11:23:49 +02:00
Andras Bacsai
5d95d8b79a fix: cancel any deployments + queue next 2023-10-17 11:10:33 +02:00
Andras Bacsai
fbb5f2ca2e fix: generate fqdn if you deleted a service app, but it requires fqdn 2023-10-17 10:37:39 +02:00
Andras Bacsai
16cbca36c1 add trademark policy 2023-10-17 10:37:26 +02:00
Andras Bacsai
24a578bedb Merge pull request #1327 from theh2so4/main
[+] Update
2023-10-17 10:31:25 +02:00
Andras Bacsai
36dc479772 fix: service status check is a bit better 2023-10-17 10:17:03 +02:00
Andras Bacsai
83d6e488e4 Merge pull request #1330 from seii/fix/raspbian-support
Add Raspbian support to install.sh
2023-10-17 09:50:57 +02:00
Seii
a4d358d512 Add Raspbian support to install.sh 2023-10-16 22:11:51 -06:00
TheH2SO4
c0c197101d [+] Update
💄 **Styling**:

-> ℹ️ **Alphabetical order**: Changed the order of the templates and set them on a alphabetical order.
-> ℹ️ **More accurate descriptions**: Created more accurate descriptions for the templates: `Appsmith, Appwrite, Fider, Ghost, Umami` and `Uptime Kuma`.
2023-10-15 23:48:33 +02:00
Andras Bacsai
62e39ccc7f Merge pull request #1326 from coollabsio/next
v4.0.0-beta.86
2023-10-15 16:56:21 +02:00
Andras Bacsai
a88a016137 fix: build image before starting dockerfile buildpacks 2023-10-15 16:54:16 +02:00
Andras Bacsai
f4c8986ab3 Merge pull request #1324 from coollabsio/next
v4.0.0-beta.85
2023-10-14 17:40:08 +02:00
Andras Bacsai
e286eae53b Merge pull request #1322 from scmmishra/fix/redis-default-url
fix: generated redis URL string
2023-10-14 17:36:48 +02:00
Andras Bacsai
bc3e59e4ef fix: delete resource if there was an error
fix: do not refresh on containerStatusJob (db view)
2023-10-14 17:26:16 +02:00
Andras Bacsai
17ebc650c9 Merge pull request #1323 from coollabsio/next
v4.0.0-beta.84
2023-10-14 14:24:20 +02:00
Andras Bacsai
0ef386b4a8 fix: stopping a resource is now job based
ui: show status on project
2023-10-14 14:22:07 +02:00
Shivam Mishra
26fce85bb0 fix: redis URL generated 2023-10-14 12:10:12 +05:30
Andras Bacsai
2a079e3365 Merge pull request #1321 from coollabsio/next
v4.0.0-beta.83
2023-10-13 21:54:26 +02:00
Andras Bacsai
5fb5ed75c4 Merge pull request #1315 from scmmishra/patch-1
fix: docker hub URL for redis
2023-10-13 21:46:46 +02:00
Andras Bacsai
a2008fe9d1 typo 2023-10-13 21:44:04 +02:00
Andras Bacsai
3c96485e3d fix: custom dockerfile location for dockerfile buildpack
fix: able to use custom port for git cloning
2023-10-13 21:39:49 +02:00
Andras Bacsai
1c97d47ea0 fix: turn off static deployment if you switch buildpacks 2023-10-13 21:08:59 +02:00
Shivam Mishra
8f8f5878dd fix: docker hub URL 2023-10-13 22:34:46 +05:30
Andras Bacsai
6e6f39dc1f Merge pull request #1314 from coollabsio/next
v4.0.0-beta.82
2023-10-13 16:38:52 +02:00
Andras Bacsai
d2d1f984e1 fix 2023-10-13 16:32:59 +02:00
Andras Bacsai
d635e5dbae fix: backup database one-by-one. 2023-10-13 15:45:24 +02:00
Andras Bacsai
49c56524e1 fix: dev containerjobs 2023-10-13 15:17:04 +02:00
Andras Bacsai
6ced607f2a fix: timeout for instant remote processes 2023-10-13 15:16:52 +02:00
Andras Bacsai
aaa2febef4 fix: docker cleanup jobs 2023-10-13 15:16:33 +02:00
Andras Bacsai
10e6eddcfe fix: db labels 2023-10-13 15:16:22 +02:00
Andras Bacsai
2639bf92ad fix: traefik dashboard ip 2023-10-13 14:35:02 +02:00
Andras Bacsai
59eae3a44e fix: proxy check for ports, do not kill anything listening on port 80/443 2023-10-13 14:25:30 +02:00
Andras Bacsai
5aa8ccfcf4 link to default redis conf 2023-10-13 09:42:38 +02:00
Andras Bacsai
aea7cc9638 fix: show database logs in case of its not healthy and running 2023-10-13 09:40:37 +02:00
Andras Bacsai
c970907c73 fix: no backup for redis 2023-10-13 09:39:40 +02:00
Andras Bacsai
38c6c1ee40 fix: urls should be password fields 2023-10-13 09:36:37 +02:00
Andras Bacsai
a6118f5daf fix 2023-10-13 09:34:11 +02:00
Andras Bacsai
b196c138d9 fix: server ip could be hostname in self-hosted 2023-10-13 09:31:44 +02:00
Andras Bacsai
8f9949160c feat: add custom redis conf 2023-10-12 17:29:29 +02:00
Andras Bacsai
beae0b545f init: redis 2023-10-12 17:18:33 +02:00
Andras Bacsai
b8dd7704b3 update resource delete command 2023-10-12 13:35:57 +02:00
Andras Bacsai
d913be66e6 Merge pull request #1312 from coollabsio/next
v4.0.0-beta.81
2023-10-12 12:23:03 +02:00
Andras Bacsai
63de538879 seeder update 2023-10-12 12:18:26 +02:00
Andras Bacsai
1d733b2282 seeder 2023-10-12 12:11:54 +02:00
Andras Bacsai
1d0ad51fdf fix: dockerfile location feature 2023-10-12 12:01:09 +02:00
Andras Bacsai
83d00bbe3c fix: make sure to use IP address 2023-10-12 11:41:37 +02:00
Andras Bacsai
c422b4dbcf fix: isCloud in production seeder 2023-10-12 11:36:44 +02:00
Andras Bacsai
767fd334dd Merge pull request #1310 from coollabsio/next
v4.0.0-beta.80
2023-10-12 09:47:39 +02:00
Andras Bacsai
972223f01b disable docker_compose deployments 2023-10-12 09:30:27 +02:00
Andras Bacsai
9318cac189 fix: service status check
fix: containerStatusJob
fix: service form
2023-10-12 09:12:46 +02:00
Andras Bacsai
7aa991fd7c fix: service check status 10 sec 2023-10-12 08:58:08 +02:00
Andras Bacsai
5c27f43b3d move autoupdate job to actions 2023-10-12 08:56:29 +02:00
Andras Bacsai
a2f4d4ed6d fix: make sure proxy wont start in NONE mode 2023-10-12 08:51:32 +02:00
Andras Bacsai
6aca2740fb fix 2023-10-11 15:46:59 +02:00
Andras Bacsai
cd13b5b83e version++ 2023-10-11 15:39:27 +02:00
Andras Bacsai
758dbafbf1 Merge pull request #1308 from coollabsio/next
v4.0.0-beta.79
2023-10-11 15:27:21 +02:00
Andras Bacsai
f6663661df disallow robots 2023-10-11 15:07:00 +02:00
Andras Bacsai
9666099408 commit 2023-10-11 14:43:34 +02:00
Andras Bacsai
d382af6860 fix 2023-10-11 14:40:41 +02:00
Andras Bacsai
4905454269 hm 2023-10-11 14:33:18 +02:00
Andras Bacsai
ed8bd37230 disallow robots 2023-10-11 14:31:59 +02:00
Andras Bacsai
ec1a7aa893 Merge pull request #1307 from coollabsio/next
v4.0.0-beta.78
2023-10-11 14:25:27 +02:00
Andras Bacsai
62adf2c5dc fix: boarding + verification 2023-10-11 14:24:19 +02:00
Andras Bacsai
3e4538de98 update command 2023-10-11 14:12:02 +02:00
Andras Bacsai
5a7b16ea5f command: delete server 2023-10-11 14:04:21 +02:00
Andras Bacsai
aa7bc40f85 fix: send unreachable/revived notifications 2023-10-11 13:52:46 +02:00
Andras Bacsai
4fd83dc727 version++ 2023-10-11 13:51:30 +02:00
Andras Bacsai
0e451f87a9 Merge pull request #1305 from coollabsio/next
v4.0.0-beta.77
2023-10-11 13:50:56 +02:00
Andras Bacsai
0b0ae55f0b fix 2023-10-11 13:47:14 +02:00
Andras Bacsai
40ec3d9753 fix 2023-10-11 13:34:51 +02:00
Andras Bacsai
9535c8df29 fix: check localhost connection 2023-10-11 13:30:36 +02:00
Andras Bacsai
6ca1d36d5d fix: cannot remove localhost 2023-10-11 13:12:29 +02:00
Andras Bacsai
f5d16c46cb version++ 2023-10-11 13:11:52 +02:00
Andras Bacsai
725c3fd547 Merge pull request #1304 from coollabsio/next
v4.0.0-beta.76
2023-10-11 12:57:35 +02:00
Andras Bacsai
dcfcee1db6 fix: public git 2023-10-11 12:56:57 +02:00
Andras Bacsai
9f8c44d96b version++ 2023-10-11 12:33:06 +02:00
Andras Bacsai
5b74fd34f5 fix: instant save build pack change 2023-10-11 12:12:25 +02:00
Andras Bacsai
5a4c9422b2 fix: only require registry image in case of dockerimage bp 2023-10-11 12:10:40 +02:00
Andras Bacsai
81b916724e Merge pull request #1303 from coollabsio/next
v4.0.0-beta.75
2023-10-11 12:06:15 +02:00
Andras Bacsai
9540f60fa2 fix: dashboard goto link 2023-10-11 12:04:30 +02:00
Andras Bacsai
8eb1686125 fix: transactional email link 2023-10-11 12:04:23 +02:00
Andras Bacsai
daf3710a5e fix: add new team button 2023-10-11 12:04:14 +02:00
Andras Bacsai
1067f37e4d fix: deleted team and it is the current one 2023-10-11 12:03:59 +02:00
Andras Bacsai
3b3c0b94e5 version++ 2023-10-11 12:03:39 +02:00
Andras Bacsai
8b0a0d67da Merge pull request #1302 from coollabsio/next
v4.0.0-beta.74
2023-10-11 11:09:19 +02:00
Andras Bacsai
5541c135df feat: proxy logs on the ui 2023-10-11 11:00:40 +02:00
Andras Bacsai
2552cb2208 ui: able to select environment on new resource 2023-10-11 10:19:03 +02:00
Andras Bacsai
f001e9bc34 improve dashboard 2023-10-11 10:08:37 +02:00
Andras Bacsai
f943fdc5be fix: use only ip addresses for servers 2023-10-11 09:57:35 +02:00
Andras Bacsai
a4f1fcba58 move subscription to livewire + show manage subscription button for people already subscribed once 2023-10-11 09:55:05 +02:00
Andras Bacsai
68091b44fc fix: contact link 2023-10-11 09:54:01 +02:00
Andras Bacsai
9f8caac91c dev: coolify proxy access logs exposed in dev 2023-10-11 09:31:30 +02:00
Andras Bacsai
8082dc1a01 fix: use port exposed for reverse proxy 2023-10-11 09:23:31 +02:00
Andras Bacsai
a71cf5bc66 cleanup 2023-10-10 15:36:06 +02:00
Andras Bacsai
0775074509 version++ 2023-10-10 14:34:38 +02:00
Andras Bacsai
242d2fb283 Merge pull request #1300 from coollabsio/next
v4.0.0-beta.73
2023-10-10 14:29:44 +02:00
Andras Bacsai
5646818965 version++ 2023-10-10 14:26:31 +02:00
Andras Bacsai
ffc5320940 fix: backupfailed notification is forced 2023-10-10 14:17:16 +02:00
Andras Bacsai
7c10c55b1c fix: only send email if transactional email set 2023-10-10 14:14:41 +02:00
Andras Bacsai
7c96b6207a Merge pull request #1299 from coollabsio/next
v4.0.0-beta.72
2023-10-10 14:06:11 +02:00
Andras Bacsai
0be8ffbdc9 feat: add dockerfile location 2023-10-10 14:02:43 +02:00
Andras Bacsai
24fa56762e fix: database backups 2023-10-10 13:10:43 +02:00
Andras Bacsai
84d8e35411 fix: tcp proxy for dbs 2023-10-10 11:42:35 +02:00
Andras Bacsai
3d3ccc435c ui: fix 2023-10-10 11:29:33 +02:00
Andras Bacsai
be3b01472e ui: fix 2023-10-10 11:28:57 +02:00
Andras Bacsai
de6f5b1105 fix: goto 2023-10-10 11:27:39 +02:00
Andras Bacsai
14d9c06dcd feat: able to deploy docker images 2023-10-10 11:16:38 +02:00
Andras Bacsai
8abfaa1967 fix: no env goto envs from dashboard 2023-10-10 10:57:56 +02:00
Andras Bacsai
46f7ae9588 ui: updated dashboard 2023-10-10 10:56:11 +02:00
Andras Bacsai
f2c32b9aeb fixes 2023-10-09 20:37:42 +02:00
Andras Bacsai
3dab1eb92e fix: server saving 2023-10-09 20:12:03 +02:00
Andras Bacsai
9c22e01716 wip: dockerimage 2023-10-09 15:49:48 +02:00
Andras Bacsai
d1c47a4062 version++ 2023-10-09 15:22:51 +02:00
Andras Bacsai
6c3f97d9ae Merge pull request #1297 from coollabsio/next
v4.0.0-beta.71
2023-10-09 15:19:35 +02:00
Andras Bacsai
ebd8e2ce40 fix: check connection 2023-10-09 15:08:28 +02:00
Andras Bacsai
b650f3f754 fix: contact docs 2023-10-09 14:52:24 +02:00
Andras Bacsai
f33ba40478 fix help 2023-10-09 14:48:51 +02:00
Andras Bacsai
5cea9c4603 isInstanceAdmin() 2023-10-09 14:38:44 +02:00
Andras Bacsai
d32832fabc update 2023-10-09 14:32:30 +02:00
Andras Bacsai
165f0a3d4a feat: add email verification for cloud 2023-10-09 14:20:55 +02:00
Andras Bacsai
f14995200b fix: do not reset unreachable count 2023-10-09 12:41:21 +02:00
Andras Bacsai
12bb2ecc4a update 2023-10-09 12:13:30 +02:00
Andras Bacsai
933ec5741d fix: server unreachable count 2023-10-09 12:07:42 +02:00
Andras Bacsai
8004a40139 updates 2023-10-09 11:49:38 +02:00
Andras Bacsai
a6209fbe5c package updates 2023-10-09 11:45:23 +02:00
Andras Bacsai
b47c327b55 Merge pull request #1296 from coollabsio/next
fix: small
2023-10-09 11:24:06 +02:00
Andras Bacsai
25434a7acd fix: small 2023-10-09 11:23:51 +02:00
Andras Bacsai
0de042dbac Merge pull request #1295 from coollabsio/next
v4.0.0-beta.70
2023-10-09 11:23:32 +02:00
Andras Bacsai
eb9e2203b0 fix: fqdn could be null 2023-10-09 11:10:04 +02:00
Andras Bacsai
dcaa7a6ad7 fix: server validation process 2023-10-09 11:00:18 +02:00
Andras Bacsai
5b584a6c6d Merge pull request #1292 from vaporii/main
corrected 'orAGnize' to 'orGAnize'
2023-10-09 09:24:16 +02:00
vaporii
c40ea6f1da corrected 'orAGnize' to 'orGAnize' 2023-10-08 15:01:09 -05:00
Andras Bacsai
61a7b9ac94 fix: able to set base dir for Dockerfile build pack 2023-10-07 12:54:19 +02:00
Andras Bacsai
9e81416fef fix: better unreachable/revived server statuses 2023-10-07 00:51:01 +02:00
Andras Bacsai
c58706e3e4 Merge pull request #1291 from adiologydev/next
fix(create): flex wrap on server & network selection
2023-10-06 20:55:15 +02:00
Andras Bacsai
45bca8649b fix: public repository names 2023-10-06 20:48:03 +02:00
Andras Bacsai
b095b88281 fix: contribution guide 2023-10-06 20:45:35 +02:00
Aditya Tripathi
cb41584137 fix(create): flex wrap on server & network selection 2023-10-06 22:31:20 +05:30
Andras Bacsai
6659153804 version++
fix: unreachable servers
2023-10-06 15:32:46 +02:00
Andras Bacsai
44c429a224 Merge pull request #1290 from coollabsio/next
v4.0.0-beta.69
2023-10-06 14:54:14 +02:00
Andras Bacsai
fe9c501c1d ui 2023-10-06 14:51:50 +02:00
Andras Bacsai
4e94b4a0c1 fix: private repository 2023-10-06 14:50:49 +02:00
Andras Bacsai
2f4d7c0e43 feat: deploy private repo with ssh key 2023-10-06 14:39:30 +02:00
Andras Bacsai
e443fc394a fix: select branch on other git 2023-10-06 13:52:15 +02:00
Andras Bacsai
5b56c50f03 feat: init version of any git deployment 2023-10-06 13:46:42 +02:00
Andras Bacsai
3adeb2f73f fix: set smtp notifications on by default 2023-10-06 12:32:38 +02:00
Andras Bacsai
9eaa13a08a update 2023-10-06 11:17:35 +02:00
Andras Bacsai
df5a4a9667 fix: contact details in emails
fix: pricing plans
2023-10-06 11:16:29 +02:00
Andras Bacsai
26048339d6 Merge pull request #1289 from coollabsio/next
v4.0.0-beta.68
2023-10-06 10:58:12 +02:00
Andras Bacsai
d85af3fefc fix: ui for self-hosted email settings 2023-10-06 10:57:35 +02:00
Andras Bacsai
575338609b fix 2023-10-06 10:47:48 +02:00
Andras Bacsai
d32e43ef37 fix: test emails only available for user owned smtp/resend 2023-10-06 10:42:32 +02:00
Andras Bacsai
a96ef1bfab use instance settigns by default 2023-10-06 10:30:32 +02:00
Andras Bacsai
1bfedf69f2 Merge pull request #1288 from coollabsio/next
v4.0.0-beta.67
2023-10-06 10:24:13 +02:00
Andras Bacsai
208fe7d87b revert 2023-10-06 10:11:04 +02:00
Andras Bacsai
a6d58b5d72 fix: decrease max horizon processes to get lower memory usage 2023-10-06 10:10:47 +02:00
Andras Bacsai
277b4276e6 small fixes 2023-10-06 10:08:50 +02:00
Andras Bacsai
f6adc9285a fix: services - do not remove unnecessary things for now 2023-10-06 10:08:46 +02:00
Andras Bacsai
f03bbe0e95 feat: basedir / monorepo initial support 2023-10-06 10:07:25 +02:00
Andras Bacsai
535375193c cloud: add shared email option to everyone 2023-10-05 14:46:39 +02:00
Andras Bacsai
d79c063fd6 ui: notifications 2023-10-05 14:44:17 +02:00
Andras Bacsai
35f45492e3 fix: email notifications subscription fixed 2023-10-05 14:37:16 +02:00
Andras Bacsai
ab8a7893d9 composer update 2023-10-05 13:57:59 +02:00
Andras Bacsai
76b8d048d4 fix: PR deployments use the first fqdn as base 2023-10-05 13:35:16 +02:00
Andras Bacsai
0e583334e7 version++ 2023-10-05 11:39:02 +02:00
Andras Bacsai
0ad8ca224f Merge pull request #1287 from coollabsio/next
v4.0.0-beta.66
2023-10-05 11:28:50 +02:00
Andras Bacsai
050e56f69a fix: traefik labelling in case of several http and https domain added 2023-10-05 11:27:50 +02:00
Andras Bacsai
6099ac11d9 version++ 2023-10-05 11:26:45 +02:00
Andras Bacsai
adac728a60 Merge pull request #1286 from coollabsio/next
v4.0.0-beta.65
2023-10-05 11:00:56 +02:00
Andras Bacsai
e2e64e36a0 feat: disable service, required version 2023-10-05 10:58:08 +02:00
Andras Bacsai
762af66cbf add deprecated templates in dev 2023-10-05 10:48:26 +02:00
Andras Bacsai
b08f525bd4 fix: move dev data to volumes to prevent permission issues 2023-10-05 08:54:03 +02:00
Andras Bacsai
5ae16b195c remove ray 2023-10-05 08:50:01 +02:00
Andras Bacsai
91db1953ff fix: delete event to deleting 2023-10-05 08:46:26 +02:00
Andras Bacsai
1c8f92d3b7 fix: remove SERVICE_ from deployable compose 2023-10-05 08:40:17 +02:00
Andras Bacsai
4075572dbc fix: visible version number 2023-10-05 08:32:20 +02:00
Andras Bacsai
2971e360d7 revert last commits 2023-10-04 18:06:25 +02:00
Andras Bacsai
32bb2780f2 rename composes 2023-10-04 15:43:29 +02:00
Andras Bacsai
af69575b29 fix: remove SERVICE_ stuff from raw compose
feat: multiport predefined compose
fix: use predefined name as prefix for fqdn
2023-10-04 15:40:08 +02:00
Andras Bacsai
d4a7d0d25f fix: traefik labels for multiport deployments 2023-10-04 15:39:23 +02:00
Andras Bacsai
45f9def0f6 fix: dev compose files 2023-10-04 14:40:33 +02:00
Andras Bacsai
5a90eed7ef fix: compose parser updated 2023-10-04 14:40:26 +02:00
Andras Bacsai
38e1f17edf feat: multiselect removable resources 2023-10-04 14:40:04 +02:00
Andras Bacsai
1651845e20 version++ 2023-10-04 11:54:56 +02:00
Andras Bacsai
38e96548b5 Merge pull request #1285 from coollabsio/next
v4.0.0-beta.64
2023-10-04 11:33:21 +02:00
Andras Bacsai
47e4126dca fix: compose magic 2023-10-04 11:32:27 +02:00
Andras Bacsai
e0b175ab07 version++ 2023-10-04 11:05:40 +02:00
Andras Bacsai
93ec785f4f Merge pull request #1284 from coollabsio/next
v4.0.0-beta.63
2023-10-04 11:01:05 +02:00
Andras Bacsai
e849addab8 dev: switch back to /data (volume errors) 2023-10-04 10:57:44 +02:00
Andras Bacsai
a5e6975dac fix: ui 2023-10-04 10:34:44 +02:00
Andras Bacsai
4ac8e1cc67 fix: services file/dir read from server
ui: fix storages layout
2023-10-04 09:58:39 +02:00
Andras Bacsai
1a5e3a7836 sync volume script 2023-10-03 15:48:07 +02:00
Andras Bacsai
4498d1ed4b fix: service logs visible if the whole service stack is not running 2023-10-03 15:08:23 +02:00
Andras Bacsai
c8b974820b Merge pull request #1282 from coollabsio/next
fix: volume names
2023-10-03 13:26:02 +02:00
Andras Bacsai
527373e297 fix: volume names 2023-10-03 13:25:41 +02:00
Andras Bacsai
a84be8dc33 Merge pull request #1281 from coollabsio/next
v4.0.0-beta.61
2023-10-03 13:14:39 +02:00
Andras Bacsai
bd856f7f67 fix: volume names in services 2023-10-03 13:14:11 +02:00
Andras Bacsai
8ff216e5fb revert 2023-10-03 12:20:09 +02:00
Andras Bacsai
5255311a2e hmm 2023-10-03 12:14:58 +02:00
Andras Bacsai
774a245e84 Merge pull request #1280 from coollabsio/next
v4.0.0-beta.60
2023-10-03 12:12:17 +02:00
Andras Bacsai
194675c838 update commands 2023-10-03 12:08:57 +02:00
Andras Bacsai
75862ca8de rename resource delete 2023-10-03 11:59:30 +02:00
Andras Bacsai
5580a4e704 feat: delete resource command 2023-10-03 11:56:56 +02:00
Andras Bacsai
51e601a303 fix: only use _ in volume names for services 2023-10-03 11:22:35 +02:00
Andras Bacsai
734e9fd68d more explanation 2023-10-03 11:01:01 +02:00
Andras Bacsai
09fc950ae8 fix: add _data to vite ignore 2023-10-03 11:00:52 +02:00
Andras Bacsai
cf6caa279d fix: ui 2023-10-03 09:02:36 +02:00
Andras Bacsai
68c976ab70 fix: show all storages in one place for services 2023-10-03 08:48:07 +02:00
Andras Bacsai
1560ab2a50 fix: UI 2023-10-03 08:22:03 +02:00
Andras Bacsai
e3a6458506 version++ 2023-10-03 08:16:19 +02:00
Andras Bacsai
1768b9374f fix: move /data to ./_data in dev 2023-10-03 08:14:49 +02:00
Andras Bacsai
51d0a30a6c Merge pull request #1279 from coollabsio/next
v4.0.0-beta.59
2023-10-02 18:03:07 +02:00
Andras Bacsai
9701c65297 fix: predefined content for files 2023-10-02 18:02:32 +02:00
Andras Bacsai
58e3bb2571 version++ 2023-10-02 18:01:24 +02:00
Andras Bacsai
31cbd1602d Merge pull request #1278 from coollabsio/next
v4.0.0-beta.58
2023-10-02 17:21:07 +02:00
Andras Bacsai
dd5723d596 service: uptime kume hc updated 2023-10-02 17:17:49 +02:00
Andras Bacsai
620f26a6f1 fix: add destination to new services 2023-10-02 17:12:50 +02:00
Andras Bacsai
540717e809 feat: attach Coolify defined networks to services 2023-10-02 16:57:55 +02:00
Andras Bacsai
d446cd4103 feat: reset root password 2023-10-02 16:38:05 +02:00
Andras Bacsai
7d1a76570c wip 2023-10-02 15:51:06 +02:00
Andras Bacsai
e18766ec21 fix: if waitlist is disabled, redirect to register 2023-10-02 15:09:57 +02:00
Andras Bacsai
3d0354cf7e add contribution guide 2023-10-02 14:58:29 +02:00
Andras Bacsai
af5b9fced1 Merge pull request #1277 from coollabsio/next
v4.0.0-beta.57
2023-10-02 14:19:24 +02:00
Andras Bacsai
ab5202515e fix: service status 2023-10-02 14:12:19 +02:00
Andras Bacsai
f863db7ea5 only 100 2023-10-02 14:01:54 +02:00
Andras Bacsai
3adc0bdd6e fix: only show last 1000 lines 2023-10-02 14:01:16 +02:00
Andras Bacsai
97027875bf feat: container logs 2023-10-02 13:38:16 +02:00
Andras Bacsai
fd9c13009f fix: always pull helper image in dev 2023-10-02 09:37:55 +02:00
Andras Bacsai
46a72fac47 Merge pull request #1276 from alexjustesen/patch-1
wee little missing spacing
2023-10-02 09:32:33 +02:00
Andras Bacsai
5d6ee04991 fix: new volumes for services should have - instead of _ 2023-10-02 09:18:32 +02:00
Andras Bacsai
d523becb29 fix: add uuid to volume names 2023-10-02 09:08:41 +02:00
Andras Bacsai
41672f75d0 fix: only parse expose in dockerfiles if ports_exposes is empty 2023-10-02 09:08:33 +02:00
Alex Justesen
acd8541e68 wee little spacing
noticed some missing spacing during the onboarding process.
2023-10-01 19:16:37 -04:00
Andras Bacsai
ed6af777a4 fix: show real volume names 2023-10-01 20:46:49 +02:00
Andras Bacsai
05f162f4e8 version++ 2023-10-01 20:29:35 +02:00
Andras Bacsai
5e0adc3777 Merge pull request #1275 from coollabsio/next
v4.0.0-beta.56
2023-10-01 18:16:24 +02:00
Andras Bacsai
7d06fc4403 fix: do not show subscription cancelled noti 2023-10-01 18:14:24 +02:00
Andras Bacsai
0e1bcceb8e feat: able to disable container healthchecks
fix: dockerfile based deployments have hc off by default
2023-10-01 18:14:13 +02:00
Andras Bacsai
a922f2fedf version++ 2023-10-01 18:13:34 +02:00
Andras Bacsai
1e0226c8ed Merge pull request #1274 from coollabsio/next
v4.0.0-beta.55
2023-10-01 17:40:31 +02:00
Andras Bacsai
5c45908087 fix: if app settings is not saved to db 2023-10-01 17:39:57 +02:00
Andras Bacsai
aefdc76805 fix: dockerfile expose is not overwritten 2023-10-01 17:27:12 +02:00
Andras Bacsai
b3c8c881b7 Revert "fix: services should have destination as well"
This reverts commit 9ab5a1f7bd.
2023-10-01 15:31:25 +02:00
Andras Bacsai
9ab5a1f7bd fix: services should have destination as well 2023-10-01 13:59:22 +02:00
Andras Bacsai
23968e7886 version++ 2023-10-01 13:28:33 +02:00
Andras Bacsai
390d24b6d7 Merge pull request #1273 from coollabsio/next
v4.0.0-beta.54
2023-10-01 12:36:10 +02:00
Andras Bacsai
e4296345b3 fix: public repo branch selection
fix: commit sha selection in source tabs
2023-10-01 12:29:50 +02:00
Andras Bacsai
bcffbe418b fix: preview deployments name, status etc 2023-10-01 12:02:44 +02:00
Andras Bacsai
bfbee4e78f version+ 2023-10-01 11:33:15 +02:00
Andras Bacsai
2bdb44dac3 Merge pull request #1272 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:55:24 +02:00
Andras Bacsai
4daa1b8c16 fix: coolify db backup 2023-09-30 20:54:05 +02:00
Andras Bacsai
febc399568 fix: not found base_branch in git webhooks 2023-09-30 20:47:07 +02:00
Andras Bacsai
9b9d4f9941 Merge pull request #1271 from coollabsio/next
v4.0.0-beta.52
2023-09-30 20:27:12 +02:00
Andras Bacsai
f49b87870c fix: backups are now working again 2023-09-30 20:26:42 +02:00
Andras Bacsai
ed49d4e3a0 Merge pull request #1264 from coollabsio/next
v4.0.0-beta.51
2023-09-30 16:02:09 +02:00
Andras Bacsai
7811c75139 fix: deploykey branch 2023-09-30 15:59:39 +02:00
Andras Bacsai
068a1b4bc4 fix: preselect branc on private repos 2023-09-30 15:57:30 +02:00
Andras Bacsai
c618d912db fix: if public repository does not have a main branch 2023-09-30 15:51:01 +02:00
Andras Bacsai
3d43f2127a fix: respect server fqdn 2023-09-30 15:39:40 +02:00
Andras Bacsai
79fde593a9 fix: service volume read from filesystem
fix: edit compose moved to dialog
2023-09-30 15:08:40 +02:00
Andras Bacsai
64ce41df0f fix: file/dir based volumes are now read from the server 2023-09-29 21:38:11 +02:00
Andras Bacsai
23e205b6cd fix: docker cleanup should be a job by server 2023-09-29 14:26:42 +02:00
Andras Bacsai
4161ea7eb6 fix: show source on all type of applications 2023-09-29 14:26:31 +02:00
Andras Bacsai
77037f8933 ui: fix previews to preview 2023-09-29 14:26:19 +02:00
Andras Bacsai
bdcc0c8de5 fix: only show manually added private keys on server view 2023-09-29 11:01:58 +02:00
Andras Bacsai
ac133875fa fix: remove private key in case you removed a github app 2023-09-29 11:01:40 +02:00
Andras Bacsai
f4819a849b remove plausible for now 2023-09-29 10:53:33 +02:00
Andras Bacsai
2cd1785a92 version++ 2023-09-29 10:53:28 +02:00
421 changed files with 14506 additions and 3054 deletions

View File

@@ -1,11 +1,3 @@
############################################################################################################
# Development Environment
# User and group id for the user that will run the application inside the container
# Run in your terminal: `id -u` and `id -g` and that's the results
USERID=
GROUPID=
############################################################################################################
APP_NAME=Coolify-localhost
APP_ID=development
APP_ENV=local
@@ -13,6 +5,7 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_PORT=8000
MUX_ENABLED=false
DUSK_DRIVER_URL=http://selenium:4444

View File

@@ -13,7 +13,7 @@ env:
jobs:
amd64:
runs-on: [self-hosted, x64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io

View File

@@ -10,7 +10,7 @@ env:
jobs:
amd64:
runs-on: [self-hosted, x64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io

View File

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

View File

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

37
CONTRIBUTION.md Normal file
View File

@@ -0,0 +1,37 @@
# Contributing
> "First, thanks for considering to contribute to my project.
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
You can ask for guidance anytime on our
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
## Code Contribution
### 1) Setup your development environment
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
### 2) Set your environment variables
- Copy [.env.development.example](./.env.development.example) to .env.
## 3) Start & setup Coolify
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
### 4) Start development
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
Mails are caught by Mailpit: `localhost:8025`
## New Service Contribution
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).

View File

@@ -4,42 +4,48 @@ Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Verc
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
Image if you could have the ease of a cloud but with your own servers. That is **Coolify**.
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
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
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).
# 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.
There are hundreds of people using it for managing their client's applications, freelancers, hobbyists, businesses.
By subscribing to the cloud version, you get the Coolify server for the same price, but with:
- High-availability
- Free email notifications
- Better support
- Less maintenance for you
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
You can find the installation script [here](./scripts/install.sh).
# Support
## Support
Contact us [here](https://coolify.io/docs/contact).
Contact us [here](https://docs.coollabs.io/contact).
## Recognitions
# Recognitions
<p>
<a href="https://news.ycombinator.com/item?id=26624341">
<img
style="width: 250px; height: 54px;" width="250" height="54"
@@ -47,14 +53,17 @@ Contact us [here](https://docs.coollabs.io/contact).
src="https://hackernews-badge.vercel.app/api?id=26624341"
/>
</a>
</p>
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An&#0032;open&#0045;source&#0032;&#0038;&#0032;self&#0045;hostable&#0032;Heroku&#0044;&#0032;Netlify&#0032;alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## 💰 Financial Contributors
<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
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)!
@@ -74,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/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>
## 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)

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Actions\Application;
use App\Models\Application;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
{
use AsAction;
public function handle(Application $application)
{
$server = $application->destination->server;
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
}
}
// TODO: make notification for application
// $application->environment->project->team->notify(new StatusChanged($application));
}
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace App\Actions\Database;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{
$internalPort = null;
$type = $database->getMorphClass();
$network = data_get($database, 'destination.network');
$server = data_get($database, 'destination.server');
$containerName = data_get($database, 'uuid');
$proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$databaseType = $database->databaseType();
$network = data_get($database, 'service.destination.network');
$server = data_get($database, 'service.destination.server');
$proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) {
case 'standalone-mariadb':
$type = 'App\Models\StandaloneMariadb';
$containerName = "mariadb-{$database->service->uuid}";
break;
case 'standalone-mongodb':
$type = 'App\Models\StandaloneMongodb';
$containerName = "mongodb-{$database->service->uuid}";
break;
case 'standalone-mysql':
$type = 'App\Models\StandaloneMysql';
$containerName = "mysql-{$database->service->uuid}";
break;
case 'standalone-postgresql':
$type = 'App\Models\StandalonePostgresql';
$containerName = "postgresql-{$database->service->uuid}";
break;
case 'standalone-redis':
$type = 'App\Models\StandaloneRedis';
$containerName = "redis-{$database->service->uuid}";
break;
}
}
if ($type === 'App\Models\StandaloneRedis') {
$internalPort = 6379;
} else if ($type === 'App\Models\StandalonePostgresql') {
$internalPort = 5432;
} else if ($type === 'App\Models\StandaloneMongodb') {
$internalPort = 27017;
} else if ($type === 'App\Models\StandaloneMysql') {
$internalPort = 3306;
} else if ($type === 'App\Models\StandaloneMariadb') {
$internalPort = 3306;
}
$configuration_dir = database_proxy_dir($database->uuid);
$nginxconf = <<<EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
stream {
server {
listen $database->public_port;
proxy_pass $containerName:$internalPort;
}
}
EOF;
$dockerfile = <<< EOF
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$proxyContainerName => [
'build' => [
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'container_name' => $proxyContainerName,
'restart' => RESTART_MODE,
'ports' => [
"$database->public_port:$database->public_port",
],
'networks' => [
$network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'stat /etc/nginx/nginx.conf || exit 1',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 3,
'start_period' => '1s'
],
]
],
'networks' => [
$network => [
'external' => true,
'name' => $network,
'attachable' => true,
]
]
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $server);
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMariadb
{
use AsAction;
public StandaloneMariadb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMariadb $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if ($this->database->destination->server->isDrainLogActivated()) {
$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) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mariadb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true,
];
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
}
return $environment_variables->all();
}
private function add_custom_mysql()
{
if (is_null($this->database->mariadb_conf)) {
return;
}
$filename = 'custom-config.cnf';
$content = $this->database->mariadb_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMongodb
{
use AsAction;
public StandaloneMongodb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMongodb $database)
{
$this->database = $database;
$startCommand = "mongod";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'mongosh --eval "printjson(db.runCommand(\"ping\"))"'
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if ($this->database->destination->server->isDrainLogActivated()) {
$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) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/mongod.conf',
'target' => '/etc/mongo/mongod.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
}
$this->add_default_database();
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
'target' => '/docker-entrypoint-initdb.d',
'read_only' => true,
];
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
}
return $environment_variables->all();
}
private function add_custom_mongo_conf()
{
if (is_null($this->database->mongo_conf)) {
return;
}
$filename = 'mongod.conf';
$content = $this->database->mongo_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
private function add_default_database()
{
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";
$content_base64 = base64_encode($content);
$this->commands[] = "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d";
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js";
}
}

View File

@@ -0,0 +1,170 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneMysql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartMysql
{
use AsAction;
public StandaloneMysql $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMysql $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if ($this->database->destination->server->isDrainLogActivated()) {
$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) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->mysql_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true,
];
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
}
return $environment_variables->all();
}
private function add_custom_mysql()
{
if (is_null($this->database->mysql_conf)) {
return;
}
$filename = 'custom-config.cnf';
$content = $this->database->mysql_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
}

View File

@@ -2,19 +2,21 @@
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartPostgresql
{
use AsAction;
public StandalonePostgresql $database;
public array $commands = [];
public array $init_scripts = [];
public string $configuration_dir;
public function __invoke(Server $server, StandalonePostgresql $database)
public function handle(StandalonePostgresql $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
@@ -30,6 +32,8 @@ class StartPostgresql
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts();
$this->add_custom_conf();
$docker_compose = [
'version' => '3.8',
'services' => [
@@ -41,6 +45,9 @@ class StartPostgresql
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
@@ -59,7 +66,7 @@ class StartPostgresql
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => $this->database->limits_cpus,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
@@ -72,6 +79,16 @@ class StartPostgresql
]
]
];
if ($this->database->destination->server->isDrainLogActivated()) {
$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) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
@@ -91,14 +108,29 @@ class StartPostgresql
];
}
}
if (!is_null($this->database->postgres_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/custom-postgres.conf',
'target' => '/etc/postgresql/postgresql.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = [
'postgres',
'-c',
'config_file=/etc/postgresql/postgresql.conf',
];
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $server);
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
@@ -139,6 +171,9 @@ class StartPostgresql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
$environment_variables->push("PGUSER={$this->database->postgres_user}");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
@@ -163,4 +198,14 @@ class StartPostgresql
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
private function add_custom_conf()
{
if (is_null($this->database->postgres_conf)) {
return;
}
$filename = 'custom-postgres.conf';
$content = $this->database->postgres_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneRedis;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartRedis
{
use AsAction;
public StandaloneRedis $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneRedis $database)
{
$this->database = $database;
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
$this->commands = [
"echo '####### Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
];
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_redis();
$docker_compose = [
'version' => '3.8',
'services' => [
$container_name => [
'image' => $this->database->image,
'command' => $startCommand,
'container_name' => $container_name,
'environment' => $environment_variables,
'restart' => RESTART_MODE,
'networks' => [
$this->database->destination->network,
],
'labels' => [
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'redis-cli',
'ping'
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
];
if ($this->database->destination->server->isDrainLogActivated()) {
$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) {
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
}
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
if (!is_null($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind',
'source' => $this->configuration_dir . '/redis.conf',
'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true,
];
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
}
$docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo '####### {$database->name} started.'";
return remote_process($this->commands, $database->destination->server);
}
private function generate_local_persistent_volumes()
{
$local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
}
return $local_persistent_volumes;
}
private function generate_local_persistent_volumes_only_volume_names()
{
$local_persistent_volumes_names = [];
foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path) {
continue;
}
$name = $persistentStorage->name;
$local_persistent_volumes_names[$name] = [
'name' => $name,
'external' => false,
];
}
return $local_persistent_volumes_names;
}
private function generate_environment_variables()
{
$environment_variables = collect();
foreach ($this->database->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->value");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
}
return $environment_variables->all();
}
private function add_custom_redis()
{
if (is_null($this->database->redis_conf)) {
return;
}
$filename = 'redis.conf';
$content = $this->database->redis_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
{
$server = $database->destination->server;
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server
);
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
// TODO: make notification for services
// $database->environment->project->team->notify(new StatusChanged($database));
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Actions\Database;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{
$server = data_get($database, 'destination.server');
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
}
}

View File

@@ -58,6 +58,11 @@ class CreateNewUser implements CreatesNewUsers
'password' => Hash::make($input['password']),
]);
$team = $user->teams()->first();
if (isCloud()) {
$user->sendVerificationEmail();
} else {
$user->markEmailAsVerified();
}
}
// Set session variable
session(['currentTeam' => $user->currentTeam = $team]);

View File

@@ -4,26 +4,26 @@ namespace App\Actions\License;
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckResaleLicense
{
public function __invoke()
use AsAction;
public function handle()
{
try {
$settings = InstanceSettings::get();
$settings->update([
'is_resale_license_active' => false,
]);
if (isDev()) {
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
if (!$settings->resale_license) {
return;
}
// if (!$settings->resale_license) {
// return;
// }
$base_url = config('coolify.license_url');
if (isDev()) {
$base_url = 'http://host.docker.internal:8787';
}
$instance_id = config('app.id');
ray("Checking license key against $base_url/lemon/validate");

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if (!$server->isProxyShouldRun()) {
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
} else {
return false;
}
}
$status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') {
$server->proxy->set('status', 'running');
$server->save();
return false;
}
$ip = $server->ip;
if ($server->id === 0) {
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
return true;
}
}

View File

@@ -10,56 +10,47 @@ use Spatie\Activitylog\Models\Activity;
class StartProxy
{
use AsAction;
public function handle(Server $server, bool $async = true): Activity|string
public function handle(Server $server, bool $async = true): string|Activity
{
$commands = collect([]);
$proxyType = $server->proxyType();
if ($proxyType === 'none') {
return 'OK';
}
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
$commands = $commands->merge([
"apt-get update > /dev/null 2>&1 || true",
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
"command -v lsof >/dev/null || apt install -y lsof",
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
"mkdir -p $proxy_path && cd $proxy_path",
"echo '####### Creating 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",
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
"systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true",
"echo '####### Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo '####### Proxy installed successfully.'"
]);
$commands = $commands->merge(connectProxyToNetworks($server));
if ($async) {
$activity = remote_process($commands, $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
try {
$proxyType = $server->proxyType();
$commands = collect([]);
$proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
return 'OK';
$commands = $commands->merge([
"mkdir -p $proxy_path && cd $proxy_path",
"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) {
$activity = remote_process($commands, $server);
return $activity;
} else {
instant_remote_process($commands, $server);
$server->proxy->set('status', 'running');
$server->proxy->set('type', $proxyType);
$server->save();
return 'OK';
}
} catch(\Throwable $e) {
ray($e);
throw $e;
}
}
}

View File

@@ -2,12 +2,14 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use App\Models\StandaloneDocker;
class InstallDocker
{
public function __invoke(Server $server)
use AsAction;
public function handle(Server $server)
{
$dockerVersion = '24.0';
$config = base64_encode('{

View File

@@ -0,0 +1,185 @@
<?php
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
class InstallLogDrain
{
use AsAction;
public function handle(Server $server, string $type)
{
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 {
throw new \Exception('Unknown log drain type.');
}
$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
");
$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",
];
}
$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

@@ -2,16 +2,18 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings;
use App\Models\Server;
class UpdateCoolify
{
use AsAction;
public ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public function __invoke(bool $force)
public function handle(bool $force)
{
try {
$settings = InstanceSettings::get();

View File

@@ -4,23 +4,30 @@ namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Symfony\Component\Yaml\Yaml;
class StartService
{
use AsAction;
public function handle(Service $service)
{
$network = $service->destination->network;
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo '####### Creating Docker network.'";
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
$commands[] = "docker network create --attachable '{$service->uuid}' >/dev/null || true";
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
$commands[] = "echo '####### Pulling images.'";
$commands[] = "docker compose pull";
$commands[] = "echo '####### Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
$commands[] = "docker network connect $service->uuid coolify-proxy || true";
$compose = data_get($service,'docker_compose',[]);
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
foreach($serviceNames as $serviceName => $serviceConfig){
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
}
$activity = remote_process($commands, $service->server);
return $activity;
}

View File

@@ -22,5 +22,7 @@ class StopService
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
class Cloud extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:unused-servers';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Unused Servers from Cloud';
/**
* Execute the console command.
*/
public function handle()
{
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
$this->info($server->name);
});
}
}

View File

@@ -3,19 +3,79 @@
namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class Init extends Command
{
protected $signature = 'app:init';
protected $signature = 'app:init {--cleanup}';
protected $description = 'Cleanup instance related stuffs';
public function handle()
{
$this->alive();
$cleanup = $this->option('cleanup');
if ($cleanup) {
$this->cleanup_stucked_resources();
$this->cleanup_ssh();
}
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
}
private function cleanup_stucked_helper_containers() {
$servers = Server::all();
foreach ($servers as $server) {
if ($server->isFunctional()) {
CleanupHelperContainersJob::dispatch($server);
}
}
}
private function alive()
{
$id = config('app.id');
$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 {
echo "I am alive!\n";
Http::get("https://get.coollabs.io/coolify/v4/alive?appId=$id");
} catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n";
}
}
private function cleanup_ssh()
{
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()
{
// Cleanup any failed deployments
@@ -30,4 +90,167 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_resources()
{
// Cleanup any resources that are not attached to any environment or destination or server
try {
$applications = Application::all();
foreach ($applications as $application) {
if (!data_get($application, 'environment')) {
ray('Application without environment', $application->name);
$application->delete();
}
if (!data_get($application, 'destination.server')) {
ray('Application without server', $application->name);
$application->delete();
}
if (!$application->destination()) {
ray('Application without destination', $application->name);
$application->delete();
}
}
} catch (\Throwable $e) {
echo "Error in application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::all();
foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) {
ray('Postgresql without environment', $postgresql->name);
$postgresql->delete();
}
if (!data_get($postgresql, 'destination.server')) {
ray('Postgresql without server', $postgresql->name);
$postgresql->delete();
}
if (!$postgresql->destination()) {
ray('Postgresql without destination', $postgresql->name);
$postgresql->delete();
}
}
} catch (\Throwable $e) {
echo "Error in postgresql: {$e->getMessage()}\n";
}
try {
$redis = StandaloneRedis::all();
foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) {
ray('Redis without environment', $redis->name);
$redis->delete();
}
if (!data_get($redis, 'destination.server')) {
ray('Redis without server', $redis->name);
$redis->delete();
}
if (!$redis->destination()) {
ray('Redis without destination', $redis->name);
$redis->delete();
}
}
} catch (\Throwable $e) {
echo "Error in redis: {$e->getMessage()}\n";
}
try {
$mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) {
ray('Mongodb without environment', $mongodb->name);
$mongodb->delete();
}
if (!data_get($mongodb, 'destination.server')) {
ray('Mongodb without server', $mongodb->name);
$mongodb->delete();
}
if (!$mongodb->destination()) {
ray('Mongodb without destination', $mongodb->name);
$mongodb->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mongodb: {$e->getMessage()}\n";
}
try {
$mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) {
ray('Mysql without environment', $mysql->name);
$mysql->delete();
}
if (!data_get($mysql, 'destination.server')) {
ray('Mysql without server', $mysql->name);
$mysql->delete();
}
if (!$mysql->destination()) {
ray('Mysql without destination', $mysql->name);
$mysql->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mysql: {$e->getMessage()}\n";
}
try {
$mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) {
ray('Mariadb without environment', $mariadb->name);
$mariadb->delete();
}
if (!data_get($mariadb, 'destination.server')) {
ray('Mariadb without server', $mariadb->name);
$mariadb->delete();
}
if (!$mariadb->destination()) {
ray('Mariadb without destination', $mariadb->name);
$mariadb->delete();
}
}
} catch (\Throwable $e) {
echo "Error in mariadb: {$e->getMessage()}\n";
}
try {
$services = Service::all();
foreach ($services as $service) {
if (!data_get($service, 'environment')) {
ray('Service without environment', $service->name);
$service->delete();
}
if (!data_get($service, 'server')) {
ray('Service without server', $service->name);
$service->delete();
}
if (!$service->destination()) {
ray('Service without destination', $service->name);
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in service: {$e->getMessage()}\n";
}
try {
$serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) {
ray('ServiceApplication without service', $service->name);
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in serviceApplications: {$e->getMessage()}\n";
}
try {
$serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) {
ray('ServiceDatabase without service', $service->name);
$service->delete();
}
}
} catch (\Throwable $e) {
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select;
class ResourcesDelete extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'resources:delete';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete a resource from the database';
/**
* Execute the console command.
*/
public function handle()
{
$resource = select(
'What resource do you want to delete?',
['Application', 'Database', 'Service', 'Server'],
);
if ($resource === 'Application') {
$this->deleteApplication();
} elseif ($resource === 'Database') {
$this->deleteDatabase();
} elseif ($resource === 'Service') {
$this->deleteService();
} elseif ($resource === 'Server') {
$this->deleteServer();
}
}
private function deleteServer()
{
$servers = Server::all();
if ($servers->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$serversToDelete = multiselect(
label: 'What server do you want to delete?',
options: $servers->pluck('name', 'id')->sortKeys(),
);
foreach ($serversToDelete as $server) {
$toDelete = $servers->where('id', $server)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
break;
}
$toDelete->delete();
}
}
}
private function deleteApplication()
{
$applications = Application::all();
if ($applications->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$applicationsToDelete = multiselect(
'What application do you want to delete?',
$applications->pluck('name', 'id')->sortKeys(),
);
foreach ($applicationsToDelete as $application) {
$toDelete = $applications->where('id', $application)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
if (!$confirmed) {
break;
}
$toDelete->delete();
}
}
}
private function deleteDatabase()
{
$databases = StandalonePostgresql::all();
if ($databases->count() === 0) {
$this->error('There are no databases to delete.');
return;
}
$databasesToDelete = multiselect(
'What database do you want to delete?',
$databases->pluck('name', 'id')->sortKeys(),
);
foreach ($databasesToDelete as $database) {
$toDelete = $databases->where('id', $database)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete();
}
}
}
private function deleteService()
{
$services = Service::all();
if ($services->count() === 0) {
$this->error('There are no services to delete.');
return;
}
$servicesToDelete = multiselect(
'What service do you want to delete?',
$services->pluck('name', 'id')->sortKeys(),
);
foreach ($servicesToDelete as $service) {
$toDelete = $services->where('id', $service)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$toDelete->delete();
}
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Yaml\Yaml;
class ServicesGenerate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'services:generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate service-templates.yaml based on /templates/compose directory';
/**
* Execute the console command.
*/
public function handle()
{
ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
});
$serviceTemplatesJson = [];
foreach ($files as $file) {
$parsed = $this->process_file($file);
if ($parsed) {
$name = data_get($parsed, 'name');
$parsed = data_forget($parsed, 'name');
$serviceTemplatesJson[$name] = $parsed;
}
}
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
}
private function process_file($file)
{
$serviceName = str($file)->before('.yaml')->value();
$content = file_get_contents(base_path("templates/compose/$file"));
// $this->info($content);
$ignore = collect(preg_grep('/^# ignore:/', explode("\n", $content)))->values();
if ($ignore->count() > 0) {
$ignore = (bool)str($ignore[0])->after('# ignore:')->trim()->value();
} else {
$ignore = false;
}
if ($ignore) {
$this->info("Ignoring $file");
return;
}
$this->info("Processing $file");
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
} else {
$documentation = 'https://coolify.io/docs';
}
$slogan = collect(preg_grep('/^# slogan:/', explode("\n", $content)))->values();
if ($slogan->count() > 0) {
$slogan = str($slogan[0])->after('# slogan:')->trim()->value();
} else {
$slogan = str($file)->headline()->value();
}
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
if ($env_file->count() > 0) {
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
} else {
$env_file = null;
}
$tags = collect(preg_grep('/^# tags:/', explode("\n", $content)))->values();
if ($tags->count() > 0) {
$tags = str($tags[0])->after('# tags:')->trim()->explode(',')->map(function ($tag) {
return str($tag)->trim()->lower()->value();
})->values();
} else {
$tags = null;
}
$json = Yaml::parse($content);
$yaml = base64_encode(Yaml::dump($json, 10, 2));
$payload = [
'name' => $serviceName,
'documentation' => $documentation,
'slogan' => $slogan,
'compose' => $yaml,
'tags' => $tags,
];
if ($env_file) {
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
$env_file_base64 = base64_encode($env_file_content);
$payload['envs'] = $env_file_base64;
}
return $payload;
}
}

View File

@@ -16,7 +16,7 @@ class SyncBunny extends Command
*
* @var string
*/
protected $signature = 'sync:bunny {--only-template} {--only-version}';
protected $signature = 'sync:bunny {--templates} {--release}';
/**
* The console command description.
@@ -31,8 +31,8 @@ class SyncBunny extends Command
public function handle()
{
$that = $this;
$only_template = $this->option('only-template');
$only_version = $this->option('only-version');
$only_template = $this->option('templates');
$only_version = $this->option('release');
$bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = "coolcdn";

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use function Laravel\Prompts\password;
class UsersResetRoot extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'users:reset-root';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset Root Password';
/**
* Execute the console command.
*/
public function handle()
{
//
$this->info('You are about to reset the root password.');
$password = password('Give me a new password for root user: ');
$passwordAgain = password('Again');
if ($password != $passwordAgain) {
$this->error('Passwords do not match.');
return;
}
$this->info('Updating root password...');
try {
User::find(0)->update(['password' => Hash::make($password)]);
$this->info('Root password updated successfully.');
} catch (\Exception $e) {
$this->error('Failed to update root password.');
return;
}
}
}

View File

@@ -2,12 +2,12 @@
namespace App\Console;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
@@ -19,28 +19,44 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
if (isDev()) {
// $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
// $schedule->command('horizon:snapshot')->everyMinute();
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule);
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
}
}
private function pull_helper_image($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
}
}
private function check_resources($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
if (isCloud()) {
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
} else {
$servers = Server::all();
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}
}
@@ -56,7 +72,6 @@ class Kernel extends ConsoleKernel
}
private function check_scheduled_backups($schedule)
{
ray('check_scheduled_backups');
$scheduled_backups = ScheduledDatabaseBackup::all();
if ($scheduled_backups->isEmpty()) {
ray('no scheduled backups');

View File

@@ -4,6 +4,7 @@ namespace App\Exceptions;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Sentry\Laravel\Integration;
use Sentry\State\Scope;
@@ -40,6 +41,13 @@ class Handler extends ExceptionHandler
];
private InstanceSettings $settings;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401);
}
return redirect()->guest($exception->redirectTo() ?? route('login'));
}
/**
* Register the exception handling callbacks for the application.
*/
@@ -47,6 +55,7 @@ class Handler extends ExceptionHandler
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
ray($e);
return;
}
$this->settings = InstanceSettings::get();

View File

@@ -41,7 +41,7 @@ class ApplicationController extends Controller
if (!$application) {
return redirect()->route('dashboard');
}
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8);
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
}

View File

@@ -39,6 +39,10 @@ class Controller extends BaseController
} else {
$team = $user->teams()->first();
}
if (is_null(data_get($user, 'email_verified_at'))){
$user->email_verified_at = now();
$user->save();
}
Auth::login($user);
session(['currentTeam' => $team]);
return redirect()->route('dashboard');
@@ -46,15 +50,6 @@ class Controller extends BaseController
}
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
public function subscription()
{
if (!isCloud()) {
abort(404);
}
return view('subscription.index', [
'settings' => InstanceSettings::get(),
]);
}
public function license()
{

View File

@@ -19,7 +19,7 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
@@ -37,7 +37,7 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
@@ -64,10 +64,18 @@ class DatabaseController extends Controller
if (!$environment) {
return redirect()->route('dashboard');
}
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
if (!$database) {
return redirect()->route('dashboard');
}
// No backups for redis
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $database->uuid,
]);
}
return view('project.database.backups.all', [
'database' => $database,
's3s' => currentTeam()->s3s,

View File

@@ -32,8 +32,14 @@ class MagicController extends Controller
public function environments()
{
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
if (!$project) {
return response()->json([
'environments' => []
]);
}
return response()->json([
'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments
'environments' => $project->environments
]);
}

View File

@@ -6,7 +6,7 @@ use App\Models\EnvironmentVariable;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Cache;
use App\Models\StandaloneDocker;
use Illuminate\Support\Str;
class ProjectController extends Controller
@@ -59,27 +59,41 @@ class ProjectController extends Controller
return redirect()->route('dashboard');
}
if (in_array($type, DATABASE_TYPES)) {
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
if ($type->value() === "postgresql") {
$database = create_standalone_postgresql($environment->id, $destination_uuid);
} else if ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid);
} else if ($type->value() === 'mongodb') {
$database = create_standalone_mongodb($environment->id, $destination_uuid);
} else if ($type->value() === 'mysql') {
$database = create_standalone_mysql($environment->id, $destination_uuid);
}else if ($type->value() === 'mariadb') {
$database = create_standalone_mariadb($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
'project_uuid' => $project->uuid,
'environment_name' => $environment->name,
'database_uuid' => $standalone_postgresql->uuid,
'database_uuid' => $database->uuid,
]);
}
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
if ($type->startsWith('one-click-service-') && !is_null((int)$server_id)) {
$oneClickServiceName = $type->after('one-click-service-')->value();
$oneClickService = data_get($services, "$oneClickServiceName.compose");
ray($oneClickServiceName);
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
if ($oneClickDotEnvs) {
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
return !empty($value);
});
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([
'name' => "$oneClickServiceName-" . Str::random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
@@ -90,6 +104,7 @@ class ProjectController extends Controller
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
// TODO: make it shared with Service.php
switch ($command->value()) {
case 'PASSWORD':
$generatedValue = Str::password(symbols: false);

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
class ServerController extends Controller
{
use AuthorizesRequests, ValidatesRequests;
public function new_server()
{
$privateKeys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => $privateKeys,
]);
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$limit_reached = $servers >= $serverLimit;
return view('server.create', [
'limit_reached' => $limit_reached,
'private_keys' => $privateKeys,
]);
}
}

View File

@@ -38,8 +38,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\IsSubscriptionValid::class,
\App\Http\Middleware\IsBoardingFlow::class,
\App\Http\Middleware\DecideWhatToDoWithUser::class,
],

View File

@@ -33,7 +33,7 @@ class Index extends Component
public ?string $remoteServerUser = 'root';
public ?Server $createdServer = null;
public Collection|array $projects = [];
public Collection $projects;
public ?int $selectedExistingProject = null;
public ?Project $createdProject = null;
@@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
Team::find(currentTeam()->id)->update([
'show_boarding' => false
]);
ray(currentTeam());
refreshSession();
return redirect()->route('dashboard');
}
@@ -214,14 +213,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
]);
$this->getProxyType();
} catch (\Throwable $e) {
$this->dockerInstallationStarted = false;
// $this->dockerInstallationStarted = false;
return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
}
}
public function installDocker()
{
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$activity = InstallDocker::run($this->createdServer);
$this->emit('newMonitorActivity', $activity->id);
}
public function dockerInstalledOrSkipped()

View File

@@ -32,7 +32,7 @@ class CheckLicense extends Component
$this->settings->save();
if ($this->settings->resale_license) {
try {
resolve(CheckResaleLicense::class)();
CheckResaleLicense::run();
$this->emit('reloadWindow');
} catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());

View File

@@ -9,21 +9,13 @@ use Livewire\Component;
class Dashboard extends Component
{
public int $projects = 0;
public int $servers = 0;
public int $s3s = 0;
public int $resources = 0;
public $projects = [];
public $servers = [];
public function mount()
{
$this->servers = Server::ownedByCurrentTeam()->get()->count();
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
$projects = Project::ownedByCurrentTeam()->get();
foreach ($projects as $project) {
$this->resources += $project->applications->count();
$this->resources += $project->postgresqls->count();
}
$this->projects = $projects->count();
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
}
// public function getIptables()
// {

View File

@@ -16,7 +16,7 @@ class Form extends Component
protected $validationAttributes = [
'destination.name' => 'name',
'destination.network' => 'network',
'destination.server.ip' => 'IP Address',
'destination.server.ip' => 'IP Address/Domain',
];
public function submit()

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Dev;
use Livewire\Component;
class Compose extends Component
{
public string $compose = '';
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');
if ($this->base64) {
$this->compose = base64_decode($this->base64);
}
}
public function updatedCompose($value) {
$this->base64 = base64_encode($value);
}
public function render()
{
return view('livewire.dev.compose');
}
}

View File

@@ -60,12 +60,16 @@ class DeploymentNavbar extends Component
$previous_logs[] = $new_log_entry;
$this->application_deployment_queue->update([
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
}
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
queue_next_deployment($this->application);
}
}
}

View File

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

View File

@@ -3,12 +3,9 @@
namespace App\Http\Livewire\Project\Application;
use App\Models\Application;
use App\Models\InstanceSettings;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
class General extends Component
{
@@ -17,14 +14,16 @@ class General extends Component
public Application $application;
public Collection $services;
public string $name;
public string|null $fqdn;
public ?string $fqdn = null;
public string $git_repository;
public string $git_branch;
public string|null $git_commit_sha;
public ?string $git_commit_sha = null;
public string $build_pack;
public string|null $wildcard_domain = null;
public string|null $server_wildcard_domain = null;
public string|null $global_wildcard_domain = null;
public ?string $ports_exposes = null;
public $customLabels;
public bool $labelsChanged = false;
public bool $isConfigurationChanged = false;
public bool $is_static;
public bool $is_git_submodules_enabled;
@@ -52,6 +51,11 @@ class General extends Component
'application.ports_exposes' => 'required',
'application.ports_mappings' => 'nullable',
'application.dockerfile' => 'nullable',
'application.docker_registry_image_name' => 'nullable',
'application.docker_registry_image_tag' => 'nullable',
'application.dockerfile_location' => 'nullable',
'application.custom_labels' => 'nullable',
'application.dockerfile_target_build' => 'nullable',
];
protected $validationAttributes = [
'application.name' => 'name',
@@ -70,12 +74,56 @@ class General extends Component
'application.ports_exposes' => 'Ports exposes',
'application.ports_mappings' => 'Ports mappings',
'application.dockerfile' => 'Dockerfile',
'application.docker_registry_image_name' => 'Docker registry image name',
'application.docker_registry_image_tag' => 'Docker registry image tag',
'application.dockerfile_location' => 'Dockerfile location',
'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build',
];
public function mount()
{
$this->ports_exposes = $this->application->ports_exposes;
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
$this->application->isConfigurationChanged(true);
}
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if (is_null(data_get($this->application, 'custom_labels'))) {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
} else {
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
}
if (data_get($this->application, 'settings')) {
$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();
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false;
$this->application->settings->save();
}
$this->submit();
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$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;
@@ -91,58 +139,49 @@ class General extends Component
$this->application->settings->save();
$this->application->save();
$this->application->refresh();
$this->checkWildCardDomain();
$this->emit('success', 'Application settings updated!');
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
if ($force_https !== $this->is_force_https_enabled) {
$this->resetDefaultLabels(false);
}
}
protected function checkWildCardDomain()
public function getWildcardDomain()
{
$coolify_instance_settings = InstanceSettings::get();
$this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain');
$this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain');
$this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null;
$server = data_get($this->application, 'destination.server');
if ($server) {
$fqdn = generateFqdn($server, $this->application->uuid);
$this->application->fqdn = $fqdn;
$this->application->save();
$this->updatedApplicationFqdn();
}
}
public function mount()
public function resetDefaultLabels($showToaster = true)
{
$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->checkWildCardDomain();
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->ports_exposes = $this->application->ports_exposes;
$this->submit($showToaster);
}
public function generateGlobalRandomDomain()
public function updatedApplicationFqdn()
{
// Set wildcard domain based on Global wildcard domain
$url = Url::fromString($this->global_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
$this->resetDefaultLabels(false);
$this->emit('success', 'Labels reseted to default!');
}
public function generateServerRandomDomain()
{
// Set wildcard domain based on Server wildcard domain
$url = Url::fromString($this->server_wildcard_domain);
$host = $url->getHost();
$path = $url->getPath() === '/' ? '' : $url->getPath();
$scheme = $url->getScheme();
$this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path;
$this->application->save();
$this->emit('success', 'Application settings updated!');
}
public function submit()
public function submit($showToaster = true)
{
try {
$this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false);
}
if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([
'application.docker_registry_image_name' => 'required',
'application.docker_registry_image_tag' => 'required',
]);
}
if (data_get($this->application, 'fqdn')) {
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return Str::of($domain)->trim()->lower();
@@ -151,7 +190,7 @@ class General extends Component
}
if (data_get($this->application, 'dockerfile')) {
$port = get_port_from_dockerfile($this->application->dockerfile);
if ($port) {
if ($port && !$this->application->ports_exposes) {
$this->application->ports_exposes = $port;
}
}
@@ -161,10 +200,17 @@ class General extends Component
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
}
if (gettype($this->customLabels) === 'string') {
$this->customLabels = str($this->customLabels)->replace(',', "\n");
}
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
$this->application->save();
$this->emit('success', 'Application settings updated!');
$showToaster && $this->emit('success', 'Application settings updated!');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Jobs\ContainerStatusJob;
use App\Models\Application;
use Livewire\Component;
@@ -21,11 +22,13 @@ class Heading extends Component
public function check_status()
{
dispatch(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
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()
@@ -57,22 +60,23 @@ class Heading extends Component
public function stop()
{
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
if ($containers->count() === 0) {
return;
}
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$this->application->destination->server
);
$this->application->status = 'exited';
$this->application->save();
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
}
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

@@ -29,7 +29,8 @@ class Form extends Component
public function generate_real_url()
{
if (data_get($this->application, 'fqdn')) {
$url = Url::fromString($this->application->fqdn);
$firstFqdn = Str::of($this->application->fqdn)->before(',');
$url = Url::fromString($firstFqdn);
$host = $url->getHost();
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
}

View File

@@ -25,7 +25,7 @@ class Previews extends Component
public function load_prs()
{
try {
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
$this->rate_limit_remaining = $rate_limit_remaining;
$this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) {
@@ -72,8 +72,7 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
try {
$container_name = generateApplicationContainerName($this->application);
ray('Stopping container: ' . $container_name);
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();

View File

@@ -0,0 +1,159 @@
<?php
namespace App\Http\Livewire\Project;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class CloneProject extends Component
{
public string $project_uuid;
public string $environment_name;
public int $project_id;
public Project $project;
public $environments;
public $servers;
public ?Environment $environment = null;
public ?int $selectedServer = null;
public ?Server $server = null;
public $resources = [];
public string $newProjectName = '';
protected $messages = [
'selectedServer' => 'Please select a server.',
'newProjectName' => 'Please enter a name for the new project.',
];
public function mount($project_uuid)
{
$this->project_uuid = $project_uuid;
$this->project = Project::where('uuid', $project_uuid)->firstOrFail();
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id;
$this->servers = currentTeam()->servers;
$this->newProjectName = $this->project->name . ' (clone)';
}
public function render()
{
return view('livewire.project.clone-project');
}
public function selectServer($server_id)
{
$this->selectedServer = $server_id;
$this->server = $this->servers->where('id', $server_id)->first();
}
public function clone()
{
try {
$this->validate([
'selectedServer' => 'required',
'newProjectName' => 'required',
]);
$foundProject = Project::where('name', $this->newProjectName)->first();
if ($foundProject) {
throw new \Exception('Project with the same name already exists.');
}
$newProject = Project::create([
'name' => $this->newProjectName,
'team_id' => currentTeam()->id,
'description' => $this->project->description . ' (clone)',
]);
if ($this->environment->name !== 'production') {
$newProject->environments()->create([
'name' => $this->environment->name,
]);
}
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
// Clone Applications
$applications = $this->environment->applications;
$databases = $this->environment->databases();
$services = $this->environment->services;
foreach ($applications as $application) {
$uuid = (string)new Cuid2(7);
$newApplication = $application->replicate()->fill([
'uuid' => $uuid,
'fqdn' => generateFqdn($this->server, $uuid),
'status' => 'exited',
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newApplication->save();
$environmentVaribles = $application->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
'application_id' => $newApplication->id,
]);
$newEnvironmentVariable->save();
}
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newPersistentVolume = $volume->replicate()->fill([
'name' => $newApplication->uuid . '-' . str($volume->name)->afterLast('-'),
'resource_id' => $newApplication->id,
]);
$newPersistentVolume->save();
}
}
foreach ($databases as $database) {
$uuid = (string)new Cuid2(7);
$newDatabase = $database->replicate()->fill([
'uuid' => $uuid,
'status' => 'exited',
'started_at' => null,
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newDatabase->save();
$environmentVaribles = $database->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$payload = [];
if ($database->type() === 'standalone-postgresql') {
$payload['standalone_postgresql_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone-redis') {
$payload['standalone_redis_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone-mongodb') {
$payload['standalone_mongodb_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone-mysql') {
$payload['standalone_mysql_id'] = $newDatabase->id;
} else if ($database->type() === 'standalone-mariadb') {
$payload['standalone_mariadb_id'] = $newDatabase->id;
}
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
$newEnvironmentVariable->save();
}
}
foreach ($services as $service) {
$uuid = (string)new Cuid2(7);
$newService = $service->replicate()->fill([
'uuid' => $uuid,
'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer,
]);
$newService->save();
foreach ($newService->applications() as $application) {
$application->update([
'status' => 'exited',
]);
}
foreach ($newService->databases() as $database) {
$database->update([
'status' => 'exited',
]);
}
$newService->parse();
}
return redirect()->route('project.resources', [
'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name,
]);
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -3,11 +3,13 @@
namespace App\Http\Livewire\Project\Database;
use Livewire\Component;
use Spatie\Url\Url;
class BackupEdit extends Component
{
public $backup;
public $s3s;
public ?string $status = null;
public array $parameters;
protected $rules = [
@@ -16,6 +18,7 @@ class BackupEdit extends Component
'backup.number_of_backups_locally' => 'required|integer|min:1',
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
];
protected $validationAttributes = [
'backup.enabled' => 'Enabled',
@@ -23,6 +26,7 @@ class BackupEdit extends Component
'backup.number_of_backups_locally' => 'Number of Backups Locally',
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
];
protected $messages = [
'backup.s3_storage_id' => 'Select a S3 Storage',
@@ -36,12 +40,21 @@ class BackupEdit extends Component
}
}
public function delete()
{
// TODO: Delete backup from server and add a confirmation modal
$this->backup->delete();
redirect()->route('project.database.backups.all', $this->parameters);
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous();
$url = Url::fromString($previousUrl);
$url = $url->withoutQueryParameter('selectedBackupId');
$url = $url->withFragment('backups');
$url = $url->getPath() . "#{$url->getFragment()}";
return redirect()->to($url);
} else {
redirect()->route('project.database.backups.all', $this->parameters);
}
}
public function instantSave()
@@ -70,9 +83,11 @@ class BackupEdit extends Component
public function submit()
{
ray($this->backup->s3_storage_id);
try {
$this->custom_validate();
if ($this->backup->databases_to_backup == '' || $this->backup->databases_to_backup === null) {
$this->backup->databases_to_backup = null;
}
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');

View File

@@ -1,23 +0,0 @@
<?php
namespace App\Http\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackupExecution;
use Livewire\Component;
class BackupExecution extends Component
{
public ScheduledDatabaseBackupExecution $execution;
public function download()
{
}
public function delete(): void
{
delete_backup_locally($this->execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server);
$this->execution->delete();
$this->emit('success', 'Backup deleted successfully.');
$this->emit('refreshBackupExecutions');
}
}

View File

@@ -2,14 +2,59 @@
namespace App\Http\Livewire\Project\Database;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
class BackupExecutions extends Component
{
public $backup;
public $executions;
protected $listeners = ['refreshBackupExecutions'];
public $setDeletableBackup;
protected $listeners = ['refreshBackupExecutions', 'deleteBackup'];
public function deleteBackup($exeuctionId)
{
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
if (is_null($execution)) {
$this->emit('error', 'Backup execution not found.');
return;
}
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
} else {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
}
$execution->delete();
$this->emit('success', 'Backup deleted successfully.');
$this->emit('refreshBackupExecutions');
}
public function download($exeuctionId)
{
try {
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
if (is_null($execution)) {
$this->emit('error', 'Backup execution not found.');
return;
}
$filename = data_get($execution, 'filename');
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$server = $execution->scheduledDatabaseBackup->database->service->destination->server;
} else {
$server = $execution->scheduledDatabaseBackup->database->destination->server;
}
$privateKeyLocation = savePrivateKeyToFs($server);
$disk = Storage::build([
'driver' => 'sftp',
'host' => $server->ip,
'port' => $server->port,
'username' => $server->user,
'privateKey' => $privateKeyLocation,
]);
return $disk->download($filename);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshBackupExecutions(): void
{
$this->executions = $this->backup->executions;

View File

@@ -13,6 +13,6 @@ class BackupNow extends Component
dispatch(new DatabaseBackupJob(
backup: $this->backup
));
$this->emit('success', 'Backup queued. It will be available in a few minutes');
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
}
}

View File

@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
public $database;
public $frequency;
public bool $enabled = true;
public bool $save_s3 = true;
public bool $save_s3 = false;
public $s3_storage_id;
public $s3s;
@@ -22,6 +22,12 @@ class CreateScheduledBackup extends Component
'frequency' => 'Backup Frequency',
'save_s3' => 'Save to S3',
];
public function mount()
{
if ($this->s3s->count() > 0) {
$this->s3_storage_id = $this->s3s->first()->id;
}
}
public function submit(): void
{
@@ -32,7 +38,7 @@ class CreateScheduledBackup extends Component
$this->emit('error', 'Invalid Cron / Human expression.');
return;
}
ScheduledDatabaseBackup::create([
$payload = [
'enabled' => true,
'frequency' => $this->frequency,
'save_s3' => $this->save_s3,
@@ -40,8 +46,21 @@ class CreateScheduledBackup extends Component
'database_id' => $this->database->id,
'database_type' => $this->database->getMorphClass(),
'team_id' => currentTeam()->id,
]);
$this->emit('refreshScheduledBackups');
];
if ($this->database->type() === 'standalone-postgresql') {
$payload['databases_to_backup'] = $this->database->postgres_db;
} else if ($this->database->type() === 'standalone-mysql') {
$payload['databases_to_backup'] = $this->database->mysql_database;
} else if ($this->database->type() === 'standalone-mariadb') {
$payload['databases_to_backup'] = $this->database->mariadb_database;
}
$databaseBackup = ScheduledDatabaseBackup::create($payload);
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$this->emit('refreshScheduledBackups', $databaseBackup->id);
} else {
$this->emit('refreshScheduledBackups');
}
} catch (\Throwable $e) {
handleError($e, $this);
} finally {

View File

@@ -2,7 +2,12 @@
namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Jobs\ContainerStatusJob;
use Livewire\Component;
@@ -35,24 +40,28 @@ class Heading extends Component
public function stop()
{
instant_remote_process(
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
if ($this->database->is_public) {
stopPostgresProxy($this->database);
$this->database->is_public = false;
}
StopDatabase::run($this->database);
$this->database->status = 'exited';
$this->database->save();
$this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
}
public function start()
{
if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
$activity = StartPostgresql::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-redis') {
$activity = StartRedis::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mongodb') {
$activity = StartMongodb::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mysql') {
$activity = StartMysql::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
} else if ($this->database->type() === 'standalone-mariadb') {
$activity = StartMariadb::run($this->database);
$this->emit('newMonitorActivity', $activity->id);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Livewire\Project\Database\Mariadb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneMariadb;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneMariadb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.mariadb_root_password' => 'required',
'database.mariadb_user' => 'required',
'database.mariadb_password' => 'required',
'database.mariadb_database' => 'required',
'database.mariadb_conf' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mariadb_root_password' => 'Root Password',
'database.mariadb_user' => 'User',
'database.mariadb_password' => 'Password',
'database.mariadb_database' => 'Database',
'database.mariadb_conf' => 'MariaDB Configuration',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
}
}
public function submit()
{
try {
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
}
$this->validate();
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.mariadb.general');
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace App\Http\Livewire\Project\Database\Mongodb;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneMongodb;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneMongodb $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.mongo_conf' => 'nullable',
'database.mongo_initdb_root_username' => 'required',
'database.mongo_initdb_root_password' => 'required',
'database.mongo_initdb_database' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mongo_conf' => 'Mongo Configuration',
'database.mongo_initdb_root_username' => 'Root Username',
'database.mongo_initdb_root_password' => 'Root Password',
'database.mongo_initdb_database' => 'Database',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
}
}
public function submit()
{
try {
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
}
if (str($this->database->mongo_conf)->isEmpty()) {
$this->database->mongo_conf = null;
}
$this->validate();
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.mongodb.general');
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Livewire\Project\Database\Mysql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneMysql;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneMysql $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.mysql_root_password' => 'required',
'database.mysql_user' => 'required',
'database.mysql_password' => 'required',
'database.mysql_database' => 'required',
'database.mysql_conf' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mysql_root_password' => 'Root Password',
'database.mysql_user' => 'User',
'database.mysql_password' => 'Password',
'database.mysql_database' => 'Database',
'database.mysql_conf' => 'MySQL Configuration',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
}
}
public function submit()
{
try {
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
}
$this->validate();
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.mysql.general');
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandalonePostgresql;
use Exception;
use Livewire\Component;
@@ -13,7 +15,8 @@ class General extends Component
public StandalonePostgresql $database;
public string $new_filename;
public string $new_content;
public string $db_url;
public ?string $db_url = null;
public ?string $db_url_public = null;
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
@@ -25,6 +28,7 @@ class General extends Component
'database.postgres_db' => 'required',
'database.postgres_initdb_args' => 'nullable',
'database.postgres_host_auth_method' => 'nullable',
'database.postgres_conf' => 'nullable',
'database.init_scripts' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
@@ -39,6 +43,7 @@ class General extends Component
'database.postgres_db' => 'Postgres DB',
'database.postgres_initdb_args' => 'Postgres Initdb Args',
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
'database.postgres_conf' => 'Postgres Configuration',
'database.init_scripts' => 'Init Scripts',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
@@ -47,13 +52,9 @@ class General extends Component
];
public function mount()
{
$this->getDbUrl();
}
public function getDbUrl() {
$this->db_url = $this->database->getDbUrl(true);
if ($this->database->is_public) {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->destination->server->ip}:{$this->database->public_port}/{$this->database->postgres_db}";
} else {
$this->db_url = "postgres://{$this->database->postgres_user}:{$this->database->postgres_password}@{$this->database->uuid}:5432/{$this->database->postgres_db}";
$this->db_url_public = $this->database->getDbUrl();
}
}
public function instantSave()
@@ -65,20 +66,24 @@ class General extends Component
return;
}
if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...');
startPostgresProxy($this->database);
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
stopPostgresProxy($this->database);
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->getDbUrl();
$this->database->save();
} catch(\Throwable $e) {
} catch (\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function save_init_script($script)
{
@@ -93,7 +98,6 @@ class General extends Component
$collection = collect($this->database->init_scripts);
$found = $collection->firstWhere('filename', $script['filename']);
if ($found) {
ray($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->refresh();
@@ -137,6 +141,9 @@ class General extends Component
public function submit()
{
try {
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
}
$this->validate();
$this->database->save();
$this->emit('success', 'Database updated successfully.');

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Http\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneRedis;
use Exception;
use Livewire\Component;
class General extends Component
{
protected $listeners = ['refresh'];
public StandaloneRedis $database;
public ?string $db_url = null;
public ?string $db_url_public = null;
protected $rules = [
'database.name' => 'required',
'database.description' => 'nullable',
'database.redis_conf' => 'nullable',
'database.redis_password' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
];
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.redis_conf' => 'Redis Configuration',
'database.redis_password' => 'Redis Password',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
];
public function mount()
{
$this->db_url = $this->database->getDbUrl(true);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl();
}
}
public function submit()
{
try {
$this->validate();
if ($this->database->redis_conf === "") {
$this->database->redis_conf = null;
}
$this->database->save();
$this->emit('success', 'Database updated successfully.');
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function instantSave()
{
try {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = !$this->database->is_public;
return handleError($e, $this);
}
}
public function refresh(): void
{
$this->database->refresh();
}
public function render()
{
return view('livewire.project.database.redis.general');
}
}

View File

@@ -8,13 +8,33 @@ class ScheduledBackups extends Component
{
public $database;
public $parameters;
public $type;
public $selectedBackup;
public $selectedBackupId;
public $s3s;
protected $listeners = ['refreshScheduledBackups'];
protected $queryString = ['selectedBackupId'];
public function mount(): void
{
if ($this->selectedBackupId) {
$this->setSelectedBackup($this->selectedBackupId);
}
$this->parameters = get_route_parameters();
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$this->type = 'service-database';
} else {
$this->type = 'database';
}
$this->s3s = currentTeam()->s3s;
}
public function setSelectedBackup($backupId) {
$this->selectedBackupId = $backupId;
$this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId);
if (is_null($this->selectedBackup)) {
$this->selectedBackupId = null;
}
}
public function delete($scheduled_backup_id): void
{
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
@@ -22,9 +42,11 @@ class ScheduledBackups extends Component
$this->refreshScheduledBackups();
}
public function refreshScheduledBackups(): void
public function refreshScheduledBackups(?int $id = null): void
{
ray('refreshScheduledBackups');
$this->database->refresh();
if ($id) {
$this->setSelectedBackup($id);
}
}
}

View File

@@ -21,10 +21,10 @@ class DeleteEnvironment extends Component
'environment_id' => 'required|int',
]);
$environment = Environment::findOrFail($this->environment_id);
if ($environment->applications->count() > 0) {
return $this->emit('error', 'Environment has resources defined, please delete them first.');
if ($environment->isEmpty()) {
$environment->delete();
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
}
$environment->delete();
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
return $this->emit('error', 'Environment has defined resources, please delete them first.');
}
}

View File

@@ -32,8 +32,6 @@ class DockerCompose extends Component
- type: volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind
source: ./var/lib/ghost/data
target: /data

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Livewire\Project\New;
use App\Models\Application;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class DockerImage extends Component
{
public string $dockerImage = '';
public array $parameters;
public array $query;
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
}
public function submit()
{
$this->validate([
'dockerImage' => 'required'
]);
$image = Str::of($this->dockerImage)->before(':');
if (Str::of($this->dockerImage)->contains(':')) {
$tag = Str::of($this->dockerImage)->after(':');
} else {
$tag = 'latest';
}
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (!$destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
ray($image,$tag);
$application = Application::create([
'name' => 'docker-image-' . new Cuid2(7),
'repository_project_id' => 0,
'git_repository' => "coollabsio/coolify",
'git_branch' => 'main',
'build_pack' => 'dockerimage',
'ports_exposes' => 80,
'docker_registry_image_name' => $image,
'docker_registry_image_tag' => $tag,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
]);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'docker-image-' . $application->uuid,
'fqdn' => $fqdn
]);
redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid,
'environment_name' => $environment->name,
'project_uuid' => $project->uuid,
]);
}
public function render()
{
return view('livewire.project.new.docker-image');
}
}

View File

@@ -95,6 +95,7 @@ class GithubPrivateRepository extends Component
$this->loadBranchByPage();
}
}
$this->selected_branch_name = data_get($this->branches,'0.name');
}
protected function loadBranchByPage()
@@ -144,8 +145,9 @@ class GithubPrivateRepository extends Component
$application->settings->is_static = $this->is_static;
$application->settings->save();
$sslip = sslip($destination->server);
$application->fqdn = "http://{$application->uuid}.$sslip";
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save();

View File

@@ -11,6 +11,7 @@ use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Livewire\Component;
use Spatie\Url\Url;
use Illuminate\Support\Str;
class GithubPrivateRepositoryDeployKey extends Component
{
@@ -29,7 +30,7 @@ class GithubPrivateRepositoryDeployKey extends Component
public string $repository_url;
public string $branch;
protected $rules = [
'repository_url' => 'required|url',
'repository_url' => 'required',
'branch' => 'required|string',
'port' => 'required|numeric',
'is_static' => 'required|boolean',
@@ -43,10 +44,9 @@ class GithubPrivateRepositoryDeployKey extends Component
'publish_directory' => 'Publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private GithubApp|GitlabApp|string $git_source = 'other';
private ?string $git_host = null;
private string $git_repository;
private string $git_branch;
public function mount()
{
@@ -93,27 +93,42 @@ class GithubPrivateRepositoryDeployKey extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
];
} else {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'private_key_id' => $this->private_key_id,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
$sslip = sslip($destination->server);
$application->fqdn = "http://{$application->uuid}.$sslip";
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->name = generate_random_name($application->uuid);
$application->save();
@@ -132,18 +147,18 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
if ($this->branch) {
$this->git_branch = $this->branch;
} else {
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
}
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
return;
}
if (Str::of($this->repository_url)->startsWith('http')) {
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
} else {
$this->git_repository = $this->repository_url;
}
$this->git_source = 'other';
}
}

View File

@@ -26,6 +26,11 @@ class PublicGitRepository extends Component
public string $git_branch = 'main';
public int $rate_limit_remaining = 0;
public $rate_limit_reset = 0;
private object $repository_url_parsed;
public GithubApp|GitlabApp|string $git_source = 'other';
public string $git_host;
public string $git_repository;
protected $rules = [
'repository_url' => 'required|url',
'port' => 'required|numeric',
@@ -38,10 +43,6 @@ class PublicGitRepository extends Component
'is_static' => 'static',
'publish_directory' => 'publish directory',
];
private object $repository_url_parsed;
private GithubApp|GitlabApp|null $git_source = null;
private string $git_host;
private string $git_repository;
public function mount()
{
@@ -64,25 +65,34 @@ class PublicGitRepository extends Component
}
$this->emit('success', 'Application settings updated!');
}
public function load_any_git()
{
$this->branch_found = true;
}
public function load_branch()
{
try {
$this->branch_found = false;
$this->validate([
'repository_url' => 'required|url'
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
try {
$this->branch_found = false;
$this->get_git_source();
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
return handleError($e, $this);
}
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
ray($e->getMessage());
if (!$this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
$this->get_branch();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} else {
return handleError($e, $this);
}
}
@@ -97,21 +107,23 @@ class PublicGitRepository extends Component
if ($this->git_host == 'github.com') {
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
} elseif ($this->git_host == 'gitlab.com') {
$this->git_source = GitlabApp::where('name', 'Public GitLab')->first();
} elseif ($this->git_host == 'bitbucket.org') {
// Not supported yet
}
if (is_null($this->git_source)) {
throw new \Exception('Git source not found. What?!');
return;
}
$this->git_repository = $this->repository_url;
$this->git_source = 'other';
}
private function get_branch()
{
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
if ($this->git_source === 'other') {
$this->branch_found = true;
return;
}
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
$this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s');
$this->branch_found = true;
}
}
public function submit()
@@ -122,9 +134,6 @@ class PublicGitRepository extends Component
$project_uuid = $this->parameters['project_uuid'];
$environment_name = $this->parameters['environment_name'];
$this->get_git_source();
$this->git_branch = $this->selected_branch ?? $this->git_branch;
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (!$destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
@@ -137,28 +146,42 @@ class PublicGitRepository extends Component
$project = Project::where('uuid', $project_uuid)->first();
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
if ($this->git_source === 'other') {
$application_init = [
'name' => generate_random_name(),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
];
} else {
$application_init = [
'name' => generate_application_name($this->git_repository, $this->git_branch),
'git_repository' => $this->git_repository,
'git_branch' => $this->git_branch,
'build_pack' => 'nixpacks',
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'source_id' => $this->git_source->id,
'source_type' => $this->git_source->getMorphClass()
];
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();
$sslip = sslip($destination->server);
$application->fqdn = "http://{$application->uuid}.$sslip";
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;
$application->save();
return redirect()->route('project.application.configuration', [

View File

@@ -2,12 +2,11 @@
namespace App\Http\Livewire\Project\New;
use App\Models\Project;
use App\Models\Server;
use Countable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Livewire\Component;
class Select extends Component
@@ -22,13 +21,18 @@ class Select extends Component
public Collection|array $swarmDockers = [];
public array $parameters;
public Collection|array $services = [];
public Collection|array $allServices = [];
public bool $loadingServices = true;
public bool $loading = false;
public $environments = [];
public ?string $selectedEnvironment = null;
public ?string $existingPostgresqlUrl = null;
public ?string $search = null;
protected $queryString = [
'server',
'search'
];
public function mount()
@@ -37,6 +41,22 @@ class Select extends Component
if (isDev()) {
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
}
$projectUuid = data_get($this->parameters, 'project_uuid');
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
}
public function render()
{
$this->loadServices();
return view('livewire.project.new.select');
}
public function updatedSelectedEnvironment()
{
return redirect()->route('project.resources.new', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment,
]);
}
// public function addExistingPostgresql()
@@ -49,19 +69,28 @@ class Select extends Component
// }
// }
public function loadThings()
{
$this->loadServices();
$this->loadServers();
}
public function loadServices(bool $forceReload = false)
public function loadServices(bool $force = false)
{
try {
if ($forceReload) {
Cache::forget('services');
if (count($this->allServices) > 0 && !$force) {
if (!$this->search) {
$this->services = $this->allServices;
return;
}
$this->services = $this->allServices->filter(function ($service, $key) {
$tags = collect(data_get($service, 'tags', []));
return str_contains(strtolower($key), strtolower($this->search)) || $tags->contains(function ($tag) {
return str_contains(strtolower($tag), strtolower($this->search));
});
});
} else {
$this->search = null;
$this->allServices = getServiceTemplates();
$this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search));
});;
$this->emit('success', 'Successfully loaded services.');
}
$this->services = getServiceTemplates();
$this->emit('success', 'Successfully loaded services.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {

View File

@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$port = get_port_from_dockerfile($this->dockerfile);
if (!$port) {
$port = 80;
}
$application = Application::create([
'name' => 'dockerfile-' . new Cuid2(7),
'repository_project_id' => 0,
@@ -56,14 +59,15 @@ CMD ["nginx", "-g", "daemon off;"]
'environment_id' => $environment->id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
'health_check_enabled' => false,
'source_id' => 0,
'source_type' => GithubApp::class
]);
$sslip = sslip($destination->server);
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->update([
'name' => 'dockerfile-' . $application->uuid,
'fqdn' => "http://{$application->uuid}.$sslip"
'fqdn' => $fqdn
]);
redirect()->route('project.application.configuration', [

View File

@@ -0,0 +1,19 @@
<?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

@@ -2,28 +2,56 @@
namespace App\Http\Livewire\Project\Service;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\ServiceDatabase;
use Livewire\Component;
class Database extends Component
{
public ServiceDatabase $database;
public ?string $db_url_public = null;
public $fileStorages;
protected $listeners = ["refreshFileStorages"];
protected $rules = [
'database.human_name' => 'nullable',
'database.description' => 'nullable',
'database.image' => 'required',
'database.exclude_from_status' => 'required|boolean',
'database.public_port' => 'nullable|integer',
'database.is_public' => 'required|boolean',
];
public function render()
{
return view('livewire.project.service.database');
}
public function mount() {
if ($this->database->is_public) {
$this->db_url_public = $this->database->getServiceDatabaseUrl();
}
$this->refreshFileStorages();
}
public function instantSave() {
if ($this->database->is_public && !$this->database->public_port) {
$this->emit('error', 'Public port is required.');
$this->database->is_public = false;
return;
}
if ($this->database->is_public) {
if (!str($this->database->status)->startsWith('running')) {
$this->emit('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
return;
}
StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getServiceDatabaseUrl();
$this->emit('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
$this->db_url_public = null;
$this->emit('success', 'Database is no longer publicly accessible.');
}
$this->submit();
}
public function refreshFileStorages()

View File

@@ -13,6 +13,7 @@ class FileStorage extends Component
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase $service;
public string $fs_path;
public ?string $workdir = null;
protected $rules = [
'fileStorage.is_directory' => 'required',
@@ -23,22 +24,28 @@ class FileStorage extends Component
public function mount()
{
$this->service = $this->fileStorage->service;
$this->fs_path = Str::of($this->fileStorage->fs_path)->beforeLast('/');
$file = Str::of($this->fileStorage->fs_path)->afterLast('/');
if (Str::of($this->fs_path)->startsWith('.')) {
$this->fs_path = Str::of($this->fs_path)->after('.');
$this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" . $file;
}
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->service->service->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();
try {
$this->validate();
if ($this->fileStorage->is_directory) {
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->service->saveFileVolumes();
$this->fileStorage->saveStorageOnServer($this->service);
$this->emit('success', 'File updated successfully.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);
$this->fileStorage->save();
return handleError($e, $this);
}
}

View File

@@ -4,7 +4,6 @@ namespace App\Http\Livewire\Project\Service;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
class Index extends Component
@@ -14,17 +13,25 @@ class Index extends Component
public $databases;
public array $parameters;
public array $query;
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
public function checkStatus() {
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStack();
protected $listeners = ["refreshStacks", "checkStatus"];
public function render()
{
return view('livewire.project.service.index');
}
public function refreshStack()
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function checkStatus()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
$this->refreshStacks();
}
public function refreshStacks()
{
$this->applications = $this->service->applications->sort();
$this->applications->each(function ($application) {
@@ -35,30 +42,4 @@ class Index extends Component
$database->refresh();
});
}
public function mount()
{
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$this->refreshStack();
}
public function render()
{
return view('livewire.project.service.index');
}
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->refreshStack();
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -6,8 +6,8 @@ use Livewire\Component;
class Modal extends Component
{
public function serviceStatusUpdated() {
$this->emit('serviceStatusUpdated');
public function checkStatus() {
$this->emit('checkStatus');
}
public function render()
{

View File

@@ -4,7 +4,6 @@ namespace App\Http\Livewire\Project\Service;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Jobs\ContainerStatusJob;
use App\Models\Service;
use Livewire\Component;
@@ -13,20 +12,13 @@ class Navbar extends Component
public Service $service;
public array $parameters;
public array $query;
protected $listeners = ['serviceStatusUpdated'];
protected $listeners = ["checkStatus"];
public function render()
{
return view('livewire.project.service.navbar');
}
public function serviceStatusUpdated()
{
ray('serviceStatusUpdated');
$this->check_status();
}
public function check_status()
{
dispatch_sync(new ContainerStatusJob($this->service->server));
public function checkStatus() {
$this->service->refresh();
}
public function deploy()
@@ -35,10 +27,15 @@ class Navbar extends Component
$activity = StartService::run($this->service);
$this->emit('newMonitorActivity', $activity->id);
}
public function stop()
public function stop(bool $forceCleanup = false)
{
StopService::run($this->service);
$this->service->refresh();
$this->emit('success', 'Service stopped successfully.');
if ($forceCleanup) {
$this->emit('success', 'Force cleanup service successfully.');
} else {
$this->emit('success', 'Service stopped successfully.');
}
$this->emit('checkStatus');
}
}

View File

@@ -16,20 +16,30 @@ class Show extends Component
public array $parameters;
public array $query;
public Collection $services;
public $s3s;
protected $listeners = ['generateDockerCompose'];
public function mount()
{
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
try {
$this->services = collect([]);
$this->parameters = get_route_parameters();
$this->query = request()->query();
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
if ($service) {
$this->serviceApplication = $service;
$this->serviceApplication->getFilesFromServer();
} else {
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
$this->serviceDatabase->getFilesFromServer();
}
$this->s3s = currentTeam()->s3s;
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function generateDockerCompose()
{

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Livewire\Project\Service;
use Livewire\Component;
class StackForm extends Component
{
public $service;
public $fields = [];
protected $listeners = ["saveCompose"];
public $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
'service.name' => 'required',
'service.description' => 'nullable',
];
public $validationAttributes = [];
public function mount()
{
$extraFields = $this->service->extraFields();
foreach ($extraFields as $serviceName => $fields) {
foreach ($fields as $fieldKey => $field) {
$key = data_get($field, 'key');
$value = data_get($field, 'value');
$rules = data_get($field, 'rules');
$isPassword = data_get($field, 'isPassword');
$this->fields[$key] = [
"serviceName" => $serviceName,
"key" => $key,
"name" => $fieldKey,
"value" => $value,
"isPassword" => $isPassword,
];
$this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
}
public function saveCompose($raw)
{
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function submit()
{
try {
$this->validate();
$this->service->save();
$this->service->saveExtraFields($this->fields);
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
$this->emit('refreshStacks');
$this->emit('refreshEnvs');
$this->emit('success', 'Service saved successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.stack-form');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Livewire\Project\Service;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
{
return view('livewire.project.service.storage');
}
public function addNewVolume($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
$this->emit('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Shared;
use App\Actions\Service\StopService;
use App\Jobs\DeleteResourceJob;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -10,7 +10,7 @@ class Danger extends Component
{
public $resource;
public array $parameters;
public string|null $modalId = null;
public ?string $modalId = null;
public function mount()
{
@@ -20,20 +20,8 @@ class Danger extends Component
public function delete()
{
// Should be queued
try {
if ($this->resource->type() === 'service') {
$server = $this->resource->server;
StopService::run($this->resource);
} else {
$destination = data_get($this->resource, 'destination');
if ($destination) {
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
$server = $destination->server;
}
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
}
$this->resource->delete();
DeleteResourceJob::dispatchSync($this->resource);
return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name']

View File

@@ -31,11 +31,17 @@ class All extends Component
public function getDevView()
{
$this->variables = $this->resource->environment_variables->map(function ($item) {
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
return "$item->key=$item->value";
})->sort()->join('
');
if ($this->showPreview) {
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
return "$item->key=$item->value";
})->sort()->join('
');
@@ -49,16 +55,21 @@ class All extends Component
{
if ($isPreview) {
$variables = parseEnvFormatToArray($this->variablesPreview);
$existingVariables = $this->resource->environment_variables_preview();
$this->resource->environment_variables_preview()->delete();
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
$existingVariables = $this->resource->environment_variables();
$this->resource->environment_variables()->delete();
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
}
foreach ($variables as $key => $variable) {
$found = $existingVariables->where('key', $key)->first();
if ($isPreview) {
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
} else {
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
if ($found->is_shown_once) {
continue;
}
$found->value = $variable;
$found->save();
continue;
@@ -75,6 +86,18 @@ class All extends Component
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;

View File

@@ -5,7 +5,6 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str;
class Show extends Component
{
@@ -13,29 +12,45 @@ class Show extends Component
public ModelsEnvironmentVariable $env;
public ?string $modalId = null;
public bool $isDisabled = false;
public bool $isLocked = false;
public string $type;
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'key' => 'Key',
'value' => 'Value',
'is_build_time' => 'Build Time',
'is_shown_once' => 'Shown Once',
];
public function mount()
{
$this->isDisabled = false;
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
$this->modalId = new Cuid2(7);
$this->parameters = get_route_parameters();
$this->checkEnvs();
}
public function checkEnvs()
{
$this->isDisabled = false;
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL')) {
$this->isDisabled = true;
}
if ($this->env->is_shown_once) {
$this->isLocked = true;
}
}
public function lock()
{
$this->env->is_shown_once = true;
$this->env->save();
$this->checkEnvs();
$this->emit('refreshEnvs');
}
public function instantSave()
{
$this->submit();

View File

@@ -0,0 +1,52 @@
<?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

@@ -9,6 +9,7 @@ class HealthChecks extends Component
public $resource;
protected $rules = [
'resource.health_check_enabled' => 'boolean',
'resource.health_check_path' => 'string',
'resource.health_check_port' => 'nullable|string',
'resource.health_check_host' => 'string',
@@ -22,12 +23,19 @@ class HealthChecks extends Component
'resource.health_check_start_period' => 'integer',
];
public function instantSave()
{
$this->resource->save();
$this->emit('success', 'Health check updated.');
}
public function submit()
{
try {
$this->validate();
$this->resource->save();
$this->emit('saved');
$this->emit('success', 'Health check updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

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

View File

@@ -6,6 +6,7 @@ use Livewire\Component;
class Add extends Component
{
public $uuid;
public $parameters;
public string $name;
public string $mount_path;
@@ -31,8 +32,9 @@ class Add extends Component
public function submit()
{
$this->validate();
$this->emitUp('submit', [
'name' => $this->name,
$name = $this->uuid . '-' . $this->name;
$this->emit('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);

View File

@@ -8,28 +8,10 @@ use Livewire\Component;
class All extends Component
{
public $resource;
protected $listeners = ['refreshStorages', 'submit'];
protected $listeners = ['refreshStorages'];
public function refreshStorages()
{
$this->resource->refresh();
}
public function submit($data)
{
try {
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],
'host_path' => $data['host_path'],
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -11,7 +11,7 @@ class Show extends Component
public LocalPersistentVolume $storage;
public bool $isReadOnly = false;
public ?string $modalId = null;
public ?string $realName = null;
public bool $isFirst = true;
protected $rules = [
'storage.name' => 'required|string',
@@ -26,11 +26,6 @@ class Show extends Component
public function mount()
{
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
} else {
$this->realName = $this->storage->name;
}
$this->modalId = new Cuid2(7);
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Livewire\Project\Shared;
use Livewire\Component;
class Webhooks extends Component
{
public $resource;
public ?string $deploywebhook = null;
public ?string $githubManualWebhook = null;
public ?string $gitlabManualWebhook = null;
protected $rules = [
'resource.manual_webhook_secret_github' => 'nullable|string',
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
];
public function saveSecret()
{
try {
$this->validate();
$this->resource->save();
$this->emit('success','Secret Saved.');
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->deploywebhook = generateDeployWebhook($this->resource);
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
}
public function render()
{
return view('livewire.project.shared.webhooks');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Livewire\Security;
use Livewire\Component;
class ApiTokens extends Component
{
public ?string $description = null;
public $tokens = [];
public function render()
{
return view('livewire.security.api-tokens');
}
public function mount()
{
$this->tokens = auth()->user()->tokens;
}
public function addNewToken()
{
try {
$this->validate([
'description' => 'required|min:3|max:255',
]);
$token = auth()->user()->createToken($this->description);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function revoke(int $id)
{
$token = auth()->user()->tokens()->where('id', $id)->first();
$token->delete();
$this->tokens = auth()->user()->tokens;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Livewire\Server;
use App\Models\PrivateKey;
use Livewire\Component;
class Create extends Component
{
public $private_keys = [];
public bool $limit_reached = false;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
if (!isCloud()) {
$this->limit_reached = false;
return;
}
$team = currentTeam();
$servers = $team->servers->count();
['serverLimit' => $serverLimit] = $team->limits;
$this->limit_reached = $servers >= $serverLimit;
}
public function render()
{
return view('livewire.server.create');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Livewire\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Delete extends Component
{
use AuthorizesRequests;
public $server;
public function delete()
{
try {
$this->authorize('delete', $this->server);
if ($this->server->hasDefinedResources()) {
$this->emit('error', 'Server has defined resources. Please delete them first.');
return;
}
$this->server->delete();
return redirect()->route('server.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.delete');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Server\Destination;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.destination.show');
}
}

View File

@@ -4,18 +4,17 @@ namespace App\Http\Livewire\Server;
use App\Actions\Server\InstallDocker;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Form extends Component
{
use AuthorizesRequests;
public Server $server;
public $uptime;
public $dockerVersion;
public string|null $wildcard_domain = null;
public bool $isValidConnection = false;
public bool $isValidDocker = false;
public ?string $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $listeners = ['serverRefresh'];
protected $rules = [
'server.name' => 'required|min:6',
@@ -29,11 +28,11 @@ class Form extends Component
'wildcard_domain' => 'nullable|url',
];
protected $validationAttributes = [
'server.name' => 'name',
'server.description' => 'description',
'server.ip' => 'ip',
'server.user' => 'user',
'server.port' => 'port',
'server.name' => 'Name',
'server.description' => 'Description',
'server.ip' => 'IP address/Domain',
'server.user' => 'User',
'server.port' => 'Port',
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
'server.settings.is_reachable' => 'is reachable',
'server.settings.is_part_of_swarm' => 'is part of swarm'
@@ -44,59 +43,77 @@ class Form extends Component
$this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
}
public function instantSave() {
public function serverRefresh()
{
$this->validateServer();
}
public function instantSave()
{
refresh_server_connection($this->server->privateKey);
$this->validateServer();
$this->server->settings->save();
}
public function installDocker()
{
$this->emit('installDocker');
$this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$activity = InstallDocker::run($this->server);
$this->emit('newMonitorActivity', $activity->id);
}
public function validateServer()
public function checkLocalhostConnection()
{
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->emit('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return;
}
}
public function validateServer($install = true)
{
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
$uptime = $this->server->validateConnection();
if ($uptime) {
$this->uptime = $uptime;
$this->emit('success', 'Server is reachable.');
$install && $this->emit('success', 'Server is reachable.');
} else {
$this->emit('error', 'Server is not reachable.');
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
return;
}
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('success', 'Docker Engine 23+ is installed!');
$dockerInstalled = $this->server->validateDockerEngine();
if ($dockerInstalled) {
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
} else {
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
$install && $this->installDocker();
return;
}
$dockerVersion = $this->server->validateDockerEngineVersion();
if ($dockerVersion) {
$install && $this->emit('success', 'Docker Engine version is 23+.');
} else {
$install && $this->installDocker();
return;
}
} catch (\Throwable $e) {
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
return handleError($e, $this);
} finally {
$this->emit('proxyStatusUpdated');
}
}
public function delete()
{
try {
$this->authorize('delete', $this->server);
if (!$this->server->isEmpty()) {
$this->emit('error', 'Server has defined resources. Please delete them first.');
return;
}
$this->server->delete();
return redirect()->route('server.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
$this->validate();
if (isCloud() && !isDev()) {
$this->validate();
$this->validate([
'server.ip' => 'required',
]);
} else {
$this->validate();
}
$uniqueIPs = Server::all()->reject(function (Server $server) {
return $server->id === $this->server->id;
})->pluck('ip')->toArray();
@@ -104,6 +121,7 @@ class Form extends Component
$this->emit('error', 'IP address is already in use by another team.');
return;
}
refresh_server_connection($this->server->privateKey);
$this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
$this->server->settings->save();

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Livewire\Server;
use App\Models\Server;
use Livewire\Component;
class LogDrains extends Component
{
public Server $server;
public $parameters = [];
protected $rules = [
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
'server.settings.logdrain_newrelic_license_key' => 'required|string',
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
'server.settings.logdrain_highlight_project_id' => 'required|string',
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
'server.settings.logdrain_axiom_api_key' => 'required|string',
];
protected $validationAttributes = [
'server.settings.is_logdrain_newrelic_enabled' => 'New Relic log drain',
'server.settings.logdrain_newrelic_license_key' => 'New Relic license key',
'server.settings.logdrain_newrelic_base_uri' => 'New Relic base URI',
'server.settings.is_logdrain_highlight_enabled' => 'Highlight log drain',
'server.settings.logdrain_highlight_project_id' => 'Highlight project ID',
'server.settings.is_logdrain_axiom_enabled' => 'Axiom log drain',
'server.settings.logdrain_axiom_dataset_name' => 'Axiom dataset name',
'server.settings.logdrain_axiom_api_key' => 'Axiom API key',
];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($server)) {
return redirect()->route('server.all');
}
$this->server = $server;
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function configureLogDrain()
{
try {
if ($this->server->settings->is_logdrain_newrelic_enabled) {
$this->server->logDrain('newrelic');
} else if ($this->server->settings->is_logdrain_highlight_enabled) {
$this->server->logDrain('highlight');
} else if ($this->server->settings->is_logdrain_axiom_enabled) {
$this->server->logDrain('axiom');
} else {
$this->server->logDrain('none');
$this->emit('serverRefresh');
$this->emit('success', 'Log drain service stopped.');
return;
}
$this->emit('serverRefresh');
$this->emit('success', 'Log drain service started successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave(string $type)
{
try {
$ok = $this->submit($type);
if (!$ok) {
return;
}
$this->configureLogDrain();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit(string $type)
{
try {
$this->resetErrorBag();
if ($type === 'newrelic') {
$this->validate([
'server.settings.is_logdrain_newrelic_enabled' => 'required|boolean',
'server.settings.logdrain_newrelic_license_key' => 'required|string',
'server.settings.logdrain_newrelic_base_uri' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_highlight_enabled' => false,
'is_logdrain_axiom_enabled' => false,
]);
} else if ($type === 'highlight') {
$this->validate([
'server.settings.is_logdrain_highlight_enabled' => 'required|boolean',
'server.settings.logdrain_highlight_project_id' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_axiom_enabled' => false,
]);
} else if ($type === 'axiom') {
$this->validate([
'server.settings.is_logdrain_axiom_enabled' => 'required|boolean',
'server.settings.logdrain_axiom_dataset_name' => 'required|string',
'server.settings.logdrain_axiom_api_key' => 'required|string',
]);
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
'is_logdrain_highlight_enabled' => false,
]);
}
$this->server->settings->save();
$this->emit('success', 'Settings saved successfully.');
return true;
} catch (\Throwable $e) {
if ($type === 'newrelic') {
$this->server->settings->update([
'is_logdrain_newrelic_enabled' => false,
]);
} else if ($type === 'highlight') {
$this->server->settings->update([
'is_logdrain_highlight_enabled' => false,
]);
} else if ($type === 'axiom') {
$this->server->settings->update([
'is_logdrain_axiom_enabled' => false,
]);
}
handleError($e, $this);
return false;
}
}
public function render()
{
return view('livewire.server.log-drains');
}
}

View File

@@ -11,13 +11,13 @@ class ByIp extends Component
{
public $private_keys;
public $limit_reached;
public int|null $private_key_id = null;
public ?int $private_key_id = null;
public $new_private_key_name;
public $new_private_key_description;
public $new_private_key_value;
public string $name;
public string|null $description = null;
public ?string $description = null;
public string $ip;
public string $user = 'root';
public int $port = 22;
@@ -31,11 +31,11 @@ class ByIp extends Component
'port' => 'required|integer',
];
protected $validationAttributes = [
'name' => 'name',
'description' => 'description',
'ip' => 'ip',
'user' => 'user',
'port' => 'port',
'name' => 'Name',
'description' => 'Description',
'ip' => 'IP Address/Domain',
'user' => 'User',
'port' => 'Port',
];
public function mount()

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Livewire\Server\PrivateKey;
use App\Models\PrivateKey;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public ?Server $server = null;
public $privateKeys = [];
public $parameters = [];
public function mount()
{
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.private-key.show');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -11,18 +12,40 @@ class Deploy extends Component
public Server $server;
public bool $traefikDashboardAvailable = false;
public ?string $currentRoute = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
public ?string $serverIp = null;
public function mount() {
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
public function mount()
{
if ($this->server->id === 0) {
$this->serverIp = base_ip();
} else {
$this->serverIp = $this->server->ip;
}
$this->currentRoute = request()->route()->getName();
}
public function traefikDashboardAvailable(bool $data) {
public function traefikDashboardAvailable(bool $data)
{
$this->traefikDashboardAvailable = $data;
}
public function proxyStatusUpdated()
{
$this->server->refresh();
}
public function ip()
{
}
public function checkProxy()
{
try {
CheckProxy::run($this->server, true);
$this->emit('startProxyPolling');
$this->emit('proxyChecked');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function startProxy()
{
try {

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