Compare commits

...

163 Commits

Author SHA1 Message Date
Andras Bacsai
bd7fd725f6 Merge pull request #2068 from coollabsio/next
v4.0.0-beta.267
2024-04-26 10:37:47 +02:00
Andras Bacsai
3e0440ba53 feat: initial datalist 2024-04-26 10:33:55 +02:00
Andras Bacsai
83a29f8d85 Refactor parseDockerComposeFile function in shared.php to escape service labels with escapeDollarSign 2024-04-26 09:32:47 +02:00
Andras Bacsai
96883dabe3 fix tags + ui 2024-04-25 15:04:49 +02:00
Andras Bacsai
da521020f6 Fix styling issues and add new badge class in app.css 2024-04-25 14:51:25 +02:00
Andras Bacsai
ac78738061 Fix styling issues in index.blade.php 2024-04-25 14:32:37 +02:00
Andras Bacsai
b78ca71c0f Fix disabling of environment variables starting with SERVICE_FQDN or SERVICE_URL in Show.php 2024-04-25 14:29:23 +02:00
Andras Bacsai
cd7272d00a Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 14:15:44 +02:00
Andras Bacsai
cd7489c569 Update version.php to 4.0.0-beta.267 2024-04-25 14:12:43 +02:00
Andras Bacsai
1f3f369df0 Merge branch 'main' into next 2024-04-25 13:55:57 +02:00
Andras Bacsai
911e29f2ea fix: refresh public ips on start 2024-04-25 13:52:52 +02:00
Andras Bacsai
d7b359e432 Fix cleanup of stuck resources in CleanupStuckedResources.php 2024-04-25 13:44:36 +02:00
Andras Bacsai
0b2c92cbe5 able to change storage name and path 2024-04-25 13:37:45 +02:00
Andras Bacsai
f626d964c5 Fix environment variable generation in ApplicationDeploymentJob.php 2024-04-25 13:25:35 +02:00
Andras Bacsai
b0e0b38549 Merge pull request #2037 from stefankommm/fix-branch-name
Fix: Encapsulated branch name when cloning branch
2024-04-25 13:16:06 +02:00
Andras Bacsai
94af4b2c7b Add pull request template with instructions to use 'next' branch as destination 2024-04-25 13:15:29 +02:00
Andras Bacsai
0b235e146f Fix readonly attribute in source.blade.php 2024-04-25 13:10:54 +02:00
Andras Bacsai
40f3301324 Fix remove dropzone script async attribute 2024-04-25 13:10:50 +02:00
Andras Bacsai
4fe90e59a8 Fix formatting issue in select.blade.php 2024-04-25 13:01:40 +02:00
Andras Bacsai
b28611ed08 Fix typo in create-scheduled-backup.blade.php 2024-04-25 12:59:25 +02:00
Andras Bacsai
672ad22e4f fix: n8n template 2024-04-25 12:32:35 +02:00
Andras Bacsai
c17bd5ec3a fix: able to select server when creating new destination 2024-04-25 12:14:02 +02:00
Andras Bacsai
2770755f9d feat: add db name to backup notifications 2024-04-25 12:09:46 +02:00
Andras Bacsai
ee69cdbf7b fix: 500 error on edge case 2024-04-25 12:09:36 +02:00
Andras Bacsai
a72a25640f fix: do no able to delete gh app without deleting resources 2024-04-25 11:53:06 +02:00
Andras Bacsai
e86f8ddd09 fix: caddy network issue
feat: connect compose based apps to predefined network
2024-04-25 11:49:34 +02:00
Andras Bacsai
66bdb39f1a Update version numbers to 4.0.0-beta.267 2024-04-24 18:03:20 +02:00
Andras Bacsai
b499a2ab87 Merge pull request #2027 from coollabsio/next
v4.0.0-beta.266
2024-04-24 14:35:04 +02:00
Andras Bacsai
49e4482947 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:34:03 +02:00
Andras Bacsai
6e7e4250e6 Fix issue with logging environment variables in ApplicationDeploymentJob.php 2024-04-24 14:33:20 +02:00
Andras Bacsai
5a8ea15c4f Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-24 14:32:34 +02:00
Marek Štefanko
f9aa029e8e Added " " to encapsulate branch name when cloning 2024-04-20 00:10:50 +02:00
Andras Bacsai
51e918be6d Fix issue with pulling helper image in Kernel.php and ApplicationDeploymentJob.php 2024-04-18 14:01:35 +02:00
Andras Bacsai
b0170eea8f Update version numbers to 4.0.0-beta.266 2024-04-18 14:01:29 +02:00
Andras Bacsai
144b34ca2e Merge pull request #2025 from coollabsio/next
v4.0.0-beta.265
2024-04-18 13:54:23 +02:00
Andras Bacsai
676c022e41 fix: .env saved to deployment server, not to build server 2024-04-18 13:53:10 +02:00
Andras Bacsai
7c0b98bb70 Fix sorting issue in save_environment_variables() function 2024-04-18 12:37:06 +02:00
Andras Bacsai
7779713392 Fix issue with user validation in Server.php 2024-04-18 11:48:10 +02:00
Andras Bacsai
5e1396025c Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-18 11:41:58 +02:00
Andras Bacsai
d09c88f71c Fix formatting issue in deployment show.blade.php 2024-04-18 11:14:26 +02:00
Andras Bacsai
7980f21b1b Update version numbers to 4.0.0-beta.265 2024-04-18 11:14:23 +02:00
Andras Bacsai
3843994a05 Merge pull request #2022 from coollabsio/next
v4.0.0-beta.264
2024-04-18 06:58:01 +02:00
Andras Bacsai
a1b08ca037 revert .env changes 2024-04-18 06:55:47 +02:00
Andras Bacsai
144cdd11ec Update version numbers to 4.0.0-beta.264 2024-04-18 06:55:40 +02:00
Andras Bacsai
b6531cdb10 Merge pull request #2016 from coollabsio/next
v4.0.0-beta.262
2024-04-17 18:50:54 +02:00
Andras Bacsai
0eef4a5fa1 Fix DNS validation issue and check domain usage 2024-04-17 18:49:31 +02:00
Andras Bacsai
42baaf8f2d Update version numbers to 4.0.0-beta.263 2024-04-17 18:49:25 +02:00
Andras Bacsai
08d9dff8eb Merge pull request #2014 from coollabsio/next
v4.0.0-beta.262
2024-04-17 16:47:14 +02:00
Andras Bacsai
01b3aab9bc fix: $ in labels escaped 2024-04-17 16:46:12 +02:00
Andras Bacsai
fde34ef178 version++ 2024-04-17 16:46:00 +02:00
Andras Bacsai
5195abec94 Update slogans in appsmith.yaml, appwrite.yaml, babybuddy.yaml, and budge.yaml 2024-04-17 15:48:14 +02:00
Andras Bacsai
aa79acd09e Merge pull request #2012 from coollabsio/next
v4.0.0-beta.261
2024-04-17 15:34:44 +02:00
Andras Bacsai
67011ccd72 fix: base64 encode .env 2024-04-17 15:34:31 +02:00
Andras Bacsai
e2cd7fe17e fix: rollback feature 2024-04-17 15:30:08 +02:00
Andras Bacsai
4c0624f489 fix: remove lazy loading until bug figured out 2024-04-17 15:16:08 +02:00
Andras Bacsai
8c033250b1 Update version numbers to 4.0.0-beta.261 2024-04-17 14:27:53 +02:00
Andras Bacsai
05b5a6fddf Update healthcheck URLs to use 127.0.0.1 instead of localhost 2024-04-17 14:27:51 +02:00
Andras Bacsai
df5bde7b8e Merge pull request #2008 from coollabsio/next
v4.0.0-beta.260
2024-04-17 12:52:09 +02:00
Andras Bacsai
ca5f52c48c Add Tolgee service to compose file and service templates 2024-04-17 12:48:11 +02:00
Andras Bacsai
3656adf059 Add Tolgee authentication fields to Service.php 2024-04-17 12:48:04 +02:00
Andras Bacsai
4381792b05 Refactor variable name display in stack-form.blade.php 2024-04-17 12:48:01 +02:00
Andras Bacsai
0a3c20b08a Fix issue with sorting environment variables in StackForm.php 2024-04-17 12:47:57 +02:00
Andras Bacsai
4ab3f41665 Remove debug statement in remoteProcess.php 2024-04-17 12:47:45 +02:00
Andras Bacsai
10356a4376 Fix issue with sorting environment variables in ApplicationDeploymentJob.php 2024-04-17 12:13:44 +02:00
Andras Bacsai
98c2056f53 Fix issue with saving environment variables and update version numbers 2024-04-17 12:08:35 +02:00
Andras Bacsai
74e8ae4d79 Merge pull request #1982 from coollabsio/next
v4.0.0-beta.259
2024-04-17 11:43:10 +02:00
Andras Bacsai
6b890cf3b3 Update user input label and add experimental note in by-ip.blade.php 2024-04-17 11:41:46 +02:00
Andras Bacsai
d62b471afe Refactor dispatch event names to use snake_case in General.php, FileStorage.php, Storage.php, All.php, and Show.php 2024-04-17 11:32:35 +02:00
Andras Bacsai
b97240963e Fix issue with saving environment variables in ApplicationDeploymentJob.php 2024-04-17 11:23:54 +02:00
Andras Bacsai
55ed0f2374 fix: remove temporary cloudflared config 2024-04-17 11:22:43 +02:00
Andras Bacsai
2c1b3d5790 Update Supabase Studio image version in supabase.yaml 2024-04-17 11:13:23 +02:00
Andras Bacsai
9c3b757578 fix: respect .env file (for default values) 2024-04-17 11:13:20 +02:00
Andras Bacsai
bc04ad96b5 Refactor Redis connection URLs in StandaloneDragonfly.php 2024-04-17 10:54:55 +02:00
Andras Bacsai
35b07a9c18 Refactor code to add sudo prefix for certain commands in multiple files 2024-04-17 10:49:34 +02:00
Andras Bacsai
17955fc419 Refactor error message styling in deployment show.blade.php 2024-04-17 10:49:30 +02:00
Andras Bacsai
ac98f5862a Refactor environment variable helper text in blade files 2024-04-17 10:49:24 +02:00
Andras Bacsai
63758c6679 Remove debug statement in ExecuteRemoteCommand trait 2024-04-16 21:30:28 +02:00
Andras Bacsai
7c765c47e6 Refactor user input in by-ip.blade.php 2024-04-16 21:30:10 +02:00
Andras Bacsai
58523b0000 Refactor code to add sudo prefix for certain commands 2024-04-16 21:22:57 +02:00
Andras Bacsai
41f4b36593 wip: non-root 2024-04-16 20:57:54 +02:00
Andras Bacsai
a04c7831b1 wip: non-root user for remote servers 2024-04-16 15:42:38 +02:00
Andras Bacsai
1aec16a240 Refactor healthcheck configurations in Docker Compose files 2024-04-16 14:08:11 +02:00
Andras Bacsai
498626748d Update Supabase images to latest versions 2024-04-16 13:18:55 +02:00
Andras Bacsai
f24ec97607 Refactor ActivityMonitor component to fix max height option 2024-04-16 13:18:50 +02:00
Andras Bacsai
f0ad260eab Refactor ActivityMonitor component to add max height option 2024-04-16 13:14:09 +02:00
Andras Bacsai
92ee2be021 Merge pull request #1832 from estubmo/main
Upgrade Appwrite service template to 1.5
2024-04-16 12:56:54 +02:00
Andras Bacsai
02e4528f3e add and fix a lot of services 2024-04-16 12:42:12 +02:00
Andras Bacsai
f871353acc Refactor modal-input.blade.php and navbar.blade.php to improve code readability and maintainability 2024-04-16 12:41:44 +02:00
Andras Bacsai
faa1120e14 Refactor Show.php and docker.php to improve code readability and maintainability 2024-04-16 12:41:33 +02:00
Andras Bacsai
8a8000c80a Add Unleash service configuration to Service model 2024-04-16 12:41:28 +02:00
Andras Bacsai
8164610105 Refactor EditDomain.php and shared.php to improve DockerCompose parsing logic 2024-04-16 12:41:21 +02:00
Andras Bacsai
3935a3c885 Refactor ActivityMonitor component to add fullHeight option 2024-04-16 12:41:13 +02:00
Andras Bacsai
15ec39bc56 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 12:24:57 +02:00
Andras Bacsai
5dd2b4c439 Merge pull request #1990 from Dinip/add-unleash-as-a-service
Add Unleash as a service
2024-04-16 12:24:52 +02:00
Andras Bacsai
1ee05dda4b Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:59:01 +02:00
Andras Bacsai
5a86588cb5 Merge pull request #1950 from AmruthPillai/main
Add Reactive Resume as a Service
2024-04-16 11:58:54 +02:00
Andras Bacsai
af3269b362 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:47:22 +02:00
Andras Bacsai
a7a4baa06e Merge pull request #1925 from amerkestijn/main
Added ActivePieces as a service
2024-04-16 11:47:14 +02:00
Andras Bacsai
9a497b9bba Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 11:03:00 +02:00
Andras Bacsai
d2a1c449da Merge pull request #1907 from hades200082/services/logto
Added logto.io compose
2024-04-16 11:02:48 +02:00
Andras Bacsai
504524ea56 Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:54:28 +02:00
Andras Bacsai
51e22b406c Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:51:49 +02:00
Andras Bacsai
1ec81482cc Merge pull request #1900 from iamEvanYT/cloudflared
Added Cloudflared as a Service
2024-04-16 10:51:40 +02:00
Andras Bacsai
fd610414fd Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:50:28 +02:00
Andras Bacsai
1802d5d5da Add service_type field to Create.php and EditCompose.php 2024-04-16 10:44:23 +02:00
Andras Bacsai
e7e6c76b4e Refactor stop button in heading.blade.php to use modal confirmation component 2024-04-16 10:16:28 +02:00
Andras Bacsai
84c7bd63f5 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-04-16 10:12:29 +02:00
Andras Bacsai
f7187bb798 Merge pull request #1899 from kunumigab/main
Added Classicpress as a service
2024-04-16 10:12:20 +02:00
Andras Bacsai
00db5b69ab Update UI styles in resource-view.blade.php, docker-image.blade.php, select.blade.php, and public-git-repository.blade.php 2024-04-16 10:10:08 +02:00
Andras Bacsai
b16109ca56 Fix repository URL handling in PublicGitRepository.php 2024-04-16 10:09:59 +02:00
Andras Bacsai
a00b5aa136 Update .button styles in app.css 2024-04-16 10:09:56 +02:00
Andras Bacsai
0e43e5d295 Refactor General.php to update application fqdn handling 2024-04-16 09:43:42 +02:00
Andras Bacsai
9f1e2d6192 Update stack-form.blade.php to fix conditional check for fields count 2024-04-16 09:23:31 +02:00
Andras Bacsai
52c794a259 Sort containers when loading and mounting in Logs.php 2024-04-16 09:19:30 +02:00
Andras Bacsai
fcf4c5f328 Merge pull request #1828 from hades200082/services/authentik
Added Authentik identity server as a service
2024-04-16 09:07:13 +02:00
Andras Bacsai
85b33a60b3 feat: can edit file/dir volumes from ui in compose based apps 2024-04-15 19:47:17 +02:00
Andras Bacsai
c99bb4cfd7 Refactor StackForm and parseDockerComposeFile 2024-04-15 16:54:03 +02:00
Andras Bacsai
5b0a942b42 fix: make sure that confs when checking if it is changed sorted 2024-04-15 15:45:50 +02:00
Andras Bacsai
d94c9e522c Update SUPABASE_PUBLIC_URL environment variable in supabase.yaml 2024-04-15 15:25:56 +02:00
Andras Bacsai
8fab15abd3 Fix conditional check for existing tags in tags.blade.php 2024-04-15 15:25:54 +02:00
Andras Bacsai
7717304549 Merge pull request #1987 from Nicell/font-weights
Add missing font weights
2024-04-15 14:47:00 +02:00
Andras Bacsai
b3f1cc3edd Fix unnecessary debug statement in LocalFileVolume.php 2024-04-15 14:46:38 +02:00
Andras Bacsai
16f9c727f2 feat: lazy load stuffs + tell user if compose based deployments have missing envs 2024-04-15 14:23:25 +02:00
Andras Bacsai
007ba5fce9 Update advanced options ui 2024-04-15 13:44:31 +02:00
Andras Bacsai
5b36f07493 feat: literal env variables 2024-04-15 12:46:22 +02:00
Andras Bacsai
cbd2580736 Fix SSH command generation in remoteProcess.php 2024-04-15 11:46:23 +02:00
Andras Bacsai
fe872a59ca fix: helper image only pulled if required, not every 10 mins 2024-04-15 11:11:58 +02:00
Dinip
5a34fe4704 Add unleash service without database 2024-04-14 16:30:29 +01:00
Dinip
2fc3dcce7e Add unleash as a service 2024-04-14 16:21:30 +01:00
Nick Winans
1a932fb749 Add missing font weights 2024-04-14 01:03:58 +02:00
Andras Bacsai
f8a2ee9a8d Update GitHub app styling in project creation form 2024-04-13 21:14:50 +02:00
Andras Bacsai
a972d456ba Update maintenance driver and store in app.php 2024-04-13 21:09:20 +02:00
Andras Bacsai
e9f4c9e687 Update text color in 503 error page 2024-04-13 21:04:38 +02:00
Andras Bacsai
58f7cdc15e Merge pull request #1983 from Emahhh/main
Added a suggestion on an error popup
2024-04-13 16:24:38 +02:00
Andras Bacsai
3f9b0643ee fix: check each required binaries one-by-one 2024-04-13 16:23:44 +02:00
Andras Bacsai
24bd4da94b Update horizon.php and docker-compose.prod.yml 2024-04-13 15:16:57 +02:00
Andras Bacsai
a18b4ffd0f Add allow_promotion_codes option to subscribeStripe method 2024-04-13 14:57:11 +02:00
Eirik Mo
c9e032e350 Update appwrite.yaml 2024-04-13 08:04:49 +02:00
Emanuele B
e1679ad433 Added a suggestion on an error popup 2024-04-13 00:03:36 +02:00
Andras Bacsai
933e395945 feat: dynamic mux time 2024-04-12 23:50:23 +02:00
Andras Bacsai
1f64a00bbf Update version numbers to 4.0.0-beta.259 2024-04-12 23:50:16 +02:00
Andras Bacsai
7eebc0c3a5 Fix bug in login functionality 2024-04-12 23:35:03 +02:00
Andras Bacsai
a2c9dabd77 Ignore service-templates.json in production build workflow 2024-04-12 23:34:44 +02:00
Andras Bacsai
6187c826b4 Merge pull request #1863 from hamanuha/main
Supabase Fixes: Default database name
2024-04-12 23:33:30 +02:00
Andras Bacsai
b8987963b3 Merge pull request #1973 from w3cj/fix-supabase-template
fix #1966: update supabase template
2024-04-12 23:32:24 +02:00
w3cj
0168ef55f0 fix #1966: update supabase template 2024-04-12 07:07:20 -06:00
Amruth Pillai
c3da3499d8 Add "Reactive Resume" to Self-Hostable Services 2024-04-08 16:45:26 +02:00
Eirik Mo
4d8553f959 Merge branch 'next' into main 2024-04-02 22:10:52 +02:00
Eirik Mo
eee1534da7 feat: upgrade Appwrite service template to 1.5 2024-04-02 22:09:39 +02:00
Eirik Mo
38c20f5737 Merge branch 'main' of https://github.com/estubmo/coolify 2024-04-02 21:07:50 +02:00
amerkestijn
ba85c5159d Added the ActivePieces service 2024-03-31 18:44:20 +02:00
amerkestijn
0388c5d5bc Added the ActivePieces service 2024-03-31 18:39:44 +02:00
Evan
3714c80adb Update cloudflared.yaml 2024-03-31 05:53:09 +08:00
Lee Conlin
735c1a23ea Added logto.io compose 2024-03-27 17:17:45 +00:00
iamEvan
88cfc96bd3 Cloudflared Service 2024-03-26 20:34:21 +00:00
Gabriel Peralta
11f389289d Added Classicpress as a service
Wordpress alternative without block editor (Gutenberg), Useful for small sites & lightweight blogs.

PR includes 3 version's with or without database, also logo.svg
2024-03-26 13:58:52 -04:00
Eirik Mo
553c1eacfc Merge branch 'coollabsio:main' into main 2024-03-21 09:39:22 +01:00
Manuel
0032d3cf6c Fix db_name quotes 2024-03-19 11:58:46 +01:00
Manuel
bd81cc1cc4 Use db name parameter for jwt sql 2024-03-19 10:35:50 +01:00
Manuel
bc36fcb722 Supabase Fixes: Default database name 2024-03-18 20:23:50 +01:00
Eirik Mo
11cd553949 Merge branch 'next' into main 2024-03-14 19:21:20 +01:00
Eirik Mo
45a3f82c8d feat: upgrade Appwrite service template to 1.5 2024-03-11 17:57:45 +01:00
Lee Conlin
e9c9a51b8d Added Authentik identity server as a service 2024-03-09 21:50:56 +00:00
183 changed files with 2740 additions and 993 deletions

1
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1 @@
> Always use `next` branch as destination branch for PRs, not `main`

View File

@@ -3,6 +3,8 @@ name: Production Build (v4)
on:
push:
branches: ["main"]
paths-ignore:
- templates/service-templates.json
env:
REGISTRY: ghcr.io

View File

@@ -99,7 +99,7 @@ class StartClickhouse
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -149,9 +149,9 @@ class StartDatabaseProxy
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
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",
"echo '{$dockerfile_base64}' | base64 -d | tee $configuration_dir/Dockerfile > /dev/null",
"echo '{$nginxconf_base64}' | base64 -d | tee $configuration_dir/nginx.conf > /dev/null",
"echo '{$dockercompose_base64}' | base64 -d | tee $configuration_dir/docker-compose.yaml > /dev/null",
"docker compose --project-directory {$configuration_dir} pull",
"docker compose --project-directory {$configuration_dir} up --build -d",
], $server);

View File

@@ -100,7 +100,7 @@ class StartDragonfly
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -107,7 +107,7 @@ class StartKeydb
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -100,7 +100,7 @@ class StartMariadb
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMariadb
$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}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -116,7 +116,7 @@ class StartMongodb
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -184,13 +184,13 @@ class StartMongodb
$filename = 'mongod.conf';
$content = $this->database->mongo_conf;
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
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";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/01-default-database.js > /dev/null";
}
}

View File

@@ -100,7 +100,7 @@ class StartMysql
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -171,6 +171,6 @@ class StartMysql
$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}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -122,7 +122,7 @@ class StartPostgresql
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";
@@ -197,7 +197,7 @@ class StartPostgresql
$filename = data_get($init_script, 'filename');
$content = data_get($init_script, 'content');
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/docker-entrypoint-initdb.d/{$filename} > /dev/null";
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
@@ -209,6 +209,6 @@ class StartPostgresql
$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}";
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
}

View File

@@ -111,7 +111,7 @@ class StartRedis
}
$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";
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
$readme = generate_readme_file($this->database->name, now());
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
$this->commands[] = "echo 'Pulling {$database->image} image.'";

View File

@@ -16,11 +16,11 @@ class CheckConfiguration
return 'OK';
}
$proxy_path = $server->proxyPath();
$proxy_configuration = instant_remote_process([
$payload = [
"mkdir -p $proxy_path",
"cat $proxy_path/docker-compose.yml",
], $server, false);
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || !$proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;

View File

@@ -13,8 +13,9 @@ class CheckProxy
if ($server->proxyType() === 'NONE') {
return false;
}
if (!$server->validateConnection()) {
throw new \Exception("Server Connection Error");
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
if (!$uptime) {
throw new \Exception($error);
}
if (!$server->isProxyShouldRun()) {
if ($fromUI) {

View File

@@ -23,7 +23,7 @@ class SaveConfiguration
return instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
"echo '$docker_compose_yml_base64' | base64 -d | tee $proxy_path/docker-compose.yml > /dev/null",
], $server);
}
}

View File

@@ -30,16 +30,18 @@ class StartProxy
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
"docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'"
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",

View File

@@ -29,8 +29,9 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
"mkdir -p /tmp/cloudflared && cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d > docker-compose.yml",
"mkdir -p /tmp/cloudflared",
"cd /tmp/cloudflared",
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans",
@@ -39,6 +40,11 @@ class ConfigureCloudflared
} catch (\Throwable $e) {
ray($e);
throw $e;
} finally {
$commands = collect([
"rm -fr /tmp/cloudflared",
]);
instant_remote_process($commands, $server);
}
}
}

View File

@@ -48,20 +48,28 @@ class InstallDocker
if ($supported_os_type->contains('debian')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update -y",
"command -v jq >/dev/null || apt install -y curl wget git jq",
"apt-get update -y",
"command -v curl >/dev/null || apt install -y curl",
"command -v wget >/dev/null || apt install -y wget",
"command -v git >/dev/null || apt install -y git",
"command -v jq >/dev/null || apt install -y jq",
]);
} else if ($supported_os_type->contains('rhel')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || dnf install -y curl wget git jq",
"command -v curl >/dev/null || dnf install -y curl",
"command -v wget >/dev/null || dnf install -y wget",
"command -v git >/dev/null || dnf install -y git",
"command -v jq >/dev/null || dnf install -y jq",
]);
} else if ($supported_os_type->contains('sles')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v jq >/dev/null || zypper update -y",
"command -v jq >/dev/null || zypper install -y curl wget git jq",
"zypper update -y",
"command -v curl >/dev/null || zypper install -y curl",
"command -v wget >/dev/null || zypper install -y wget",
"command -v git >/dev/null || zypper install -y git",
"command -v jq >/dev/null || zypper install -y jq",
]);
} else {
throw new \Exception('Unsupported OS');
@@ -70,10 +78,13 @@ class InstallDocker
"echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"",
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
"jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null",
"mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify",
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
"mv /etc/docker/daemon.json.appended /etc/docker/daemon.json",
"echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker",

View File

@@ -168,10 +168,10 @@ Files:
$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",
"echo '{$parsers}' | base64 -d | tee $parsers_config > /dev/null",
"echo '{$config}' | base64 -d | tee $fluent_bit_config > /dev/null",
"echo '{$compose}' | base64 -d | tee $compose_path > /dev/null",
"echo '{$readme}' | base64 -d | tee $readme_path > /dev/null",
"test -f $config_path/.env && rm $config_path/.env",
];

View File

@@ -62,7 +62,7 @@ class CleanupStuckedResources extends Command
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($keydbs as $keydb) {
echo "Deleting stuck keydb: {$keydb->name}\n";
$redis->forceDelete();
$keydb->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
@@ -71,7 +71,7 @@ class CleanupStuckedResources extends Command
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($dragonflies as $dragonfly) {
echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
$redis->forceDelete();
$dragonfly->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
@@ -80,7 +80,7 @@ class CleanupStuckedResources extends Command
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($clickhouses as $clickhouse) {
echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
$redis->forceDelete();
$clickhouse->forceDelete();
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";

View File

@@ -20,6 +20,7 @@ class Init extends Command
public function handle()
{
$this->alive();
get_public_ips();
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
if ($cleanup_deployments) {
@@ -56,6 +57,7 @@ class Init extends Command
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources');
}
private function restore_coolify_db_backup()
{
try {

View File

@@ -31,7 +31,7 @@ class Kernel extends ConsoleKernel
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {

View File

@@ -56,7 +56,7 @@ class Handler extends ExceptionHandler
{
$this->reportable(function (Throwable $e) {
if (isDev()) {
// return;
return;
}
if ($e instanceof RuntimeException) {
return;

View File

@@ -49,6 +49,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private string $deployment_uuid;
private int $pull_request_id;
private string $commit;
private bool $rollback;
private bool $force_rebuild;
private bool $restart_only;
@@ -117,6 +118,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
$this->pull_request_id = $this->application_deployment_queue->pull_request_id;
$this->commit = $this->application_deployment_queue->commit;
$this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server;
@@ -322,7 +324,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->prepare_builder_image();
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}")
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null")
],
);
$this->generate_image_names();
@@ -391,7 +393,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]);
$this->save_environment_variables();
// Build new container to limit downtime.
@@ -565,7 +567,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
@@ -708,19 +710,80 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function save_environment_variables()
{
$envs = collect([]);
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
if ($this->pull_request_id !== 0) {
$this->env_filename = ".env-pr-$this->pull_request_id";
foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
if ($this->application->environment_variables_preview->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
} else {
$this->env_filename = ".env";
foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value);
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal) {
$real_value = '\'' . $real_value . '\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$envs->push($env->key . '=' . $real_value);
}
// Add PORT if not exists, use the first port as default
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$envs->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$envs->push("HOST=0.0.0.0");
}
if ($this->application->environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$envs->push("SOURCE_COMMIT={$this->commit}");
} else {
$envs->push("SOURCE_COMMIT=unknown");
}
}
$envs = $envs->sort(function ($a, $b) {
return strpos($a, '$') === false ? -1 : 1;
});
}
if ($envs->isEmpty()) {
$this->env_filename = null;
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
@@ -728,17 +791,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true
]
);
return;
if ($this->use_build_server) {
$this->server = $this->build_server;
}
} else {
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d | tee $this->workdir/{$this->env_filename} > /dev/null")
],
);
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
],
[
"echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}"
]
);
// $this->execute_remote_command([
// executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
// "hidden" => true,
// "save" => "dotenv"
// ]);
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
// $this->execute_remote_command(
// [
// "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
// ]
// );
// } else {
// $this->execute_remote_command(
// [
// "command" => "rm -f $this->configuration_dir/.env",
// "hidden" => true,
// "ignore_errors" => true
// ]
// );
// }
}
@@ -973,7 +1070,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh")
],
[
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa")
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa")
@@ -993,7 +1090,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
],
);
}
if ($this->saved_outputs->get('git_commit_sha')) {
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
}
}
@@ -1124,8 +1221,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
$persistent_storages = $this->generate_local_persistent_volumes();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables($ports);
// $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
@@ -1159,8 +1256,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview));
}
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
// Check for custom HEALTHCHECK
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
@@ -1179,7 +1278,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'image' => $this->production_image_name,
'container_name' => $this->container_name,
'restart' => RESTART_MODE,
'environment' => $environment_variables,
'expose' => $ports,
'networks' => [
$this->destination->network,
@@ -1200,10 +1298,22 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]
]
];
if ($this->env_filename) {
$docker_compose['services'][$this->container_name]['env_file'] = [
$this->env_filename
];
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
// } else {
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
// }
// }
// if ($this->env_filename) {
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
// } else {
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
// }
// }
if (!is_null($this->env_filename)) {
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
}
if (!$this->custom_healthcheck_found) {
$docker_compose['services'][$this->container_name]['healthcheck'] = [
@@ -1357,8 +1467,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
$this->save_environment_variables();
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), "hidden" => true]);
}
private function generate_local_persistent_volumes()
@@ -1399,7 +1508,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
return $local_persistent_volumes_names;
}
private function generate_environment_variables($ports)
/*private function generate_environment_variables($ports)
{
$environment_variables = collect();
if ($this->pull_request_id === 0) {
@@ -1415,7 +1524,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables as $env) {
if ($env->version === '4.0.0-beta.239') {
@@ -1423,7 +1537,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
@@ -1432,7 +1551,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
if ($env->version === '4.0.0-beta.239') {
@@ -1440,7 +1564,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
if ($env->is_literal) {
$real_value = escapeDollarSign($real_value);
$environment_variables->push("$env->key='$real_value'");
} else {
$environment_variables->push("$env->key=$real_value");
}
}
}
// Add PORT if not exists, use the first port as default
@@ -1458,8 +1587,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables->push("SOURCE_COMMIT=unknown");
}
}
ray($environment_variables->all());
return $environment_variables->all();
}
}*/
private function generate_healthcheck_commands()
{
@@ -1539,7 +1669,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), "hidden" => true
@@ -1560,7 +1690,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1595,13 +1725,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d | tee {$this->workdir}/Dockerfile > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null")
],
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1614,7 +1744,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1623,7 +1753,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else {
if ($this->application->build_pack === 'nixpacks') {
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d > /artifacts/thegameplan.json"), "hidden" => true]);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), "hidden" => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), "hidden" => true
@@ -1644,7 +1774,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d > /artifacts/build.sh"), "hidden" => true
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), "hidden" => true
],
[
executeInDocker($this->deployment_uuid, "bash /artifacts/build.sh"), "hidden" => true
@@ -1762,7 +1892,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d | tee {$this->workdir}{$this->dockerfile_location} > /dev/null"),
"hidden" => true
]);
}

View File

@@ -47,7 +47,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (!$this->server->isFunctional()) {
return 'Server is not ready.';
};
$applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) {
@@ -78,6 +77,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
if (is_null($containers)) {
return;
}
$containers = format_docker_command_output_to_json($containers);
if ($containerReplicates) {
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
@@ -201,7 +201,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;

View File

@@ -2,6 +2,7 @@
namespace App\Jobs;
use App\Models\Application;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Bus\Queueable;
@@ -16,11 +17,11 @@ class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase $service)
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
{
}
public function handle()
{
$this->service->getFilesFromServer(isInit: true);
$this->resource->getFilesFromServer(isInit: true);
}
}

View File

@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
public bool $fullHeight = false;
public bool $showWaiting = false;
protected $activity;

View File

@@ -11,15 +11,17 @@ class NewActivityMonitor extends Component
public ?string $header = null;
public $activityId;
public $eventToDispatch = 'activityFinished';
public $eventData = null;
public $isPollingActive = false;
protected $activity;
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished', $eventData = null)
{
$this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->eventData = $eventData;
$this->hydrateActivity();
@@ -55,8 +57,12 @@ class NewActivityMonitor extends Component
}
return;
}
$this->dispatch($this->eventToDispatch);
ray('Dispatched event: ' . $this->eventToDispatch);
if (!is_null($this->eventData)) {
$this->dispatch($this->eventToDispatch, $this->eventData);
} else {
$this->dispatch($this->eventToDispatch);
}
ray('Dispatched event: ' . $this->eventToDispatch . ' with data: ' . $this->eventData);
}
}
}

View File

@@ -27,6 +27,8 @@ class Advanced extends Component
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.connect_to_docker_network' => 'boolean|required',
];
public function mount() {
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
@@ -54,8 +56,14 @@ class Advanced extends Component
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->application->parseCompose();
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->dispatch('configurationChanged');
}
public function submit() {
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Livewire\Component;
@@ -33,7 +34,8 @@ class General extends Component
public $parsedServiceDomains = [];
protected $listeners = [
'resetDefaultLabels'
'resetDefaultLabels',
'configurationChanged' => '$refresh'
];
protected $rules = [
'application.name' => 'required',
@@ -71,7 +73,6 @@ class General extends Component
'application.post_deployment_command' => 'nullable',
'application.post_deployment_command_container' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.watch_paths' => 'nullable',
];
@@ -107,7 +108,6 @@ class General extends Component
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.settings.is_static' => 'Is static',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.watch_paths' => 'Watch paths',
];
@@ -124,7 +124,7 @@ class General extends Component
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
$this->customLabels = $this->application->parseContainerLabels();
$this->customLabels = $this->application->parseContainerLabels();
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
@@ -156,8 +156,36 @@ class General extends Component
return;
}
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = Str::of($volume)->before(':');
$target = Str::of($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application)
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
@@ -165,7 +193,6 @@ class General extends Component
return handleError($e, $this);
} finally {
$this->initLoadingCompose = false;
}
}
public function generateDomain(string $serviceName)
@@ -184,7 +211,14 @@ class General extends Component
$this->loadComposeFile();
}
}
public function updatedApplicationFqdn() {
public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->resetDefaultLabels();
}
public function updatedApplicationBuildPack()
@@ -302,11 +336,6 @@ class General extends Component
check_domain_usage(resource: $this->application);
}
}
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->parsedServices = $this->application->parseCompose();
}
}
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();

View File

@@ -18,7 +18,6 @@ class Rollback extends Component
{
$this->parameters = get_route_parameters();
}
public function rollbackImage($commit)
{
$deployment_uuid = new Cuid2(7);
@@ -27,6 +26,7 @@ class Rollback extends Component
application: $this->application,
deployment_uuid: $deployment_uuid,
commit: $commit,
rollback: true,
force_rebuild: false,
);
return redirect()->route('project.application.deployment.show', [

View File

@@ -25,6 +25,7 @@ class Edit extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);

View File

@@ -29,6 +29,7 @@ class EnvironmentEdit extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);

View File

@@ -89,9 +89,11 @@ class PublicGitRepository extends Component
public function load_branch()
{
try {
$this->validate([
'repository_url' => 'required|url'
]);
if (str($this->repository_url)->startsWith('git@')) {
$github_instance = str($this->repository_url)->after('git@')->before(':');
$repository = str($this->repository_url)->after(':')->before('.git');
$this->repository_url = 'https://' . str($github_instance) . '/' . $repository;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -40,7 +40,7 @@ class Create extends Component
$database = create_standalone_keydb($environment->id, $destination_uuid);
} else if ($type->value() === 'dragonfly') {
$database = create_standalone_dragonfly($environment->id, $destination_uuid);
}else if ($type->value() === 'clickhouse') {
} else if ($type->value() === 'clickhouse') {
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
}
return redirect()->route('project.database.configuration', [
@@ -60,14 +60,19 @@ class Create extends Component
}
if ($oneClickService) {
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
$service = Service::create([
$service_payload = [
'name' => "$oneClickServiceName-" . str()->random(10),
'docker_compose_raw' => base64_decode($oneClickService),
'environment_id' => $environment->id,
'service_type' => $oneClickServiceName,
'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
];
if ($oneClickServiceName === 'cloudflared') {
data_set($service_payload, 'connect_to_docker_network', true);
}
$service = Service::create($service_payload);
$service->name = "$oneClickServiceName-" . $service->uuid;
$service->save();
if ($oneClickDotEnvs?->count() > 0) {

View File

@@ -13,11 +13,13 @@ class EditCompose extends Component
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
];
public function mount() {
public function mount()
{
$this->service = Service::find($this->serviceId);
}
public function saveEditedCompose() {
public function saveEditedCompose()
{
$this->dispatch('info', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
}

View File

@@ -41,7 +41,7 @@ class EditDomain extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('generateDockerCompose');
$this->application->service->parse();
$this->dispatch('refresh');
$this->dispatch('configurationChanged');
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service;
use App\Models\Application;
use App\Models\LocalFileVolume;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -12,7 +13,7 @@ use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@@ -33,6 +34,43 @@ class FileStorage extends Component
$this->fs_path = $this->fileStorage->fs_path;
}
}
public function convertToDirectory() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function convertToFile() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function delete() {
try {
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->delete();
$this->dispatch('success', 'File deleted.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('refresh_storages');
}
}
public function submit()
{
$original = $this->fileStorage->getOriginal();

View File

@@ -3,12 +3,13 @@
namespace App\Livewire\Project\Service;
use App\Models\Service;
use Illuminate\Support\Collection;
use Livewire\Component;
class StackForm extends Component
{
public Service $service;
public $fields = [];
public Collection $fields;
protected $listeners = ["saveCompose"];
public $rules = [
'service.docker_compose_raw' => 'required',
@@ -20,6 +21,7 @@ class StackForm extends Component
public $validationAttributes = [];
public function mount()
{
$this->fields = collect([]);
$extraFields = $this->service->extraFields();
foreach ($extraFields as $serviceName => $fields) {
foreach ($fields as $fieldKey => $field) {
@@ -27,18 +29,20 @@ class StackForm extends Component
$value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable');
$isPassword = data_get($field, 'isPassword');
$this->fields[$key] = [
$this->fields->put($key, [
"serviceName" => $serviceName,
"key" => $key,
"name" => $fieldKey,
"value" => $value,
"isPassword" => $isPassword,
"rules" => $rules
];
]);
$this->rules["fields.$key.value"] = $rules;
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortDesc();
}
public function saveCompose($raw)
{

View File

@@ -7,12 +7,13 @@ use Livewire\Component;
class Storage extends Component
{
protected $listeners = ['addNewVolume'];
public $resource;
public function render()
public function getListeners()
{
return view('livewire.project.service.storage');
return [
'addNewVolume',
'refresh_storages' => '$refresh',
];
}
public function addNewVolume($data)
{
@@ -27,9 +28,13 @@ class Storage extends Component
$this->resource->refresh();
$this->dispatch('success', 'Storage added successfully');
$this->dispatch('clearAddStorage');
$this->dispatch('refreshStorages');
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.service.storage');
}
}

View File

@@ -7,11 +7,13 @@ use Livewire\Component;
class Add extends Component
{
public $parameters;
public bool $shared = false;
public bool $is_preview = false;
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
public bool $is_multiline = false;
public bool $is_literal = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
@@ -19,12 +21,14 @@ class Add extends Component
'value' => 'nullable',
'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean',
'is_literal' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'is_multiline' => 'multiline',
'is_literal' => 'literal',
];
public function mount()
@@ -47,6 +51,7 @@ class Add extends Component
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline,
'is_literal' => $this->is_literal,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -58,5 +63,6 @@ class Add extends Component
$this->value = '';
$this->is_build_time = false;
$this->is_multiline = false;
$this->is_literal = false;
}
}

View File

@@ -15,8 +15,10 @@ class All extends Component
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
protected $listeners = ['refreshEnvs', 'saveKey' => 'submit'];
protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit',
];
public function mount()
{
$resourceClass = get_class($this->resource);
@@ -161,6 +163,7 @@ class All extends Component
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline'];
$environment->is_literal = $data['is_literal'];
$environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) {

View File

@@ -16,12 +16,16 @@ class Show extends Component
public bool $isLocked = false;
public bool $isSharedVariable = false;
public string $type;
protected $listeners = [
"compose_loaded" => '$refresh',
];
protected $rules = [
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean',
'env.is_literal' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -30,6 +34,7 @@ class Show extends Component
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline',
'env.is_literal' => 'Literal',
'env.is_shown_once' => 'Shown Once',
];

View File

@@ -44,7 +44,7 @@ class Logs extends Component
} else {
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
$server->containers = $containers;
$server->containers = $containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -94,6 +94,7 @@ class Logs extends Component
$this->servers = $this->servers->push($this->resource->server);
}
}
$this->containers = $this->containers->sort();
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@@ -7,10 +7,5 @@ use Livewire\Component;
class All extends Component
{
public $resource;
protected $listeners = ['refreshStorages'];
public function refreshStorages()
{
$this->resource->refresh();
}
protected $listeners = ['refresh_storages' => '$refresh'];
}

View File

@@ -39,6 +39,6 @@ class Show extends Component
public function delete()
{
$this->storage->delete();
$this->dispatch('refreshStorages');
$this->dispatch('refresh_storages');
}
}

View File

@@ -76,14 +76,14 @@ class Form extends Component
public function checkLocalhostConnection()
{
$this->submit();
$uptime = $this->server->validateConnection();
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = true;
$this->server->settings->is_usable = true;
$this->server->settings->save();
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error);
return;
}
}

View File

@@ -23,7 +23,8 @@ class Deploy extends Component
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy"
"checkProxy",
"startProxy"
];
}

View File

@@ -68,7 +68,7 @@ class NewDynamicConfiguration extends Component
}
$base64_value = base64_encode($this->value);
instant_remote_process([
"echo '{$base64_value}' | base64 -d > {$file}",
"echo '{$base64_value}' | base64 -d | tee {$file} > /dev/null",
], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();

View File

@@ -35,10 +35,11 @@ class ShowPrivateKey extends Component
public function checkConnection()
{
try {
$uptime = $this->server->validateConnection();
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
} else {
ray($error);
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.');
return;
}

View File

@@ -11,7 +11,7 @@ class ValidateAndInstall extends Component
{
public Server $server;
public int $number_of_tries = 0;
public int $max_tries = 1;
public int $max_tries = 3;
public bool $install = true;
public $uptime = null;
public $supported_os_type = null;
@@ -32,9 +32,8 @@ class ValidateAndInstall extends Component
'refresh' => '$refresh',
];
public function init(bool $install = true)
public function init(int $data = 0)
{
$this->install = $install;
$this->uptime = null;
$this->supported_os_type = null;
$this->docker_installed = null;
@@ -42,7 +41,7 @@ class ValidateAndInstall extends Component
$this->docker_compose_installed = null;
$this->proxy_started = null;
$this->error = null;
$this->number_of_tries = 0;
$this->number_of_tries = $data;
if (!$this->ask) {
$this->dispatch('validateConnection');
}
@@ -66,16 +65,15 @@ class ValidateAndInstall extends Component
} else {
$this->proxy_started = true;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function validateConnection()
{
$this->uptime = $this->server->validateConnection();
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (!$this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.';
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error;
return;
}
$this->dispatch('validateOS');
@@ -99,10 +97,10 @@ class ValidateAndInstall extends Component
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
return;
} else {
if ($this->number_of_tries == 0) {
if ($this->number_of_tries <= $this->max_tries) {
$activity = $this->server->installDocker();
$this->number_of_tries++;
$this->dispatch('newActivityMonitor', $activity->id, 'init');
$this->dispatch('newActivityMonitor', $activity->id, 'init', $this->number_of_tries);
}
return;
}

View File

@@ -69,13 +69,14 @@ class Configuration extends Component
}
$this->validate();
if ($this->settings->is_dns_validation_enabled) {
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}
check_domain_usage(domain: $this->settings->fqdn);
if ($this->settings->fqdn) check_domain_usage(domain: $this->settings->fqdn);
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
return str($dns)->trim()->lower();

View File

@@ -54,6 +54,7 @@ class PricingPlans extends Component
return;
}
$payload = [
'allow_promotion_codes' => true,
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Livewire\Tags;
use App\Models\ApplicationDeploymentQueue;
use Livewire\Component;
class Deployments extends Component
{
public $deployments_per_tag_per_server = [];
public $resource_ids = [];
public function render()
{
return view('livewire.tags.deployments');
}
public function get_deployments()
{
try {
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $this->resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
}

View File

@@ -20,32 +20,22 @@ class Index extends Component
public $webhook = null;
public $deployments_per_tag_per_server = [];
public function updatedTag()
public function tag_updated()
{
if ($this->tag == "") {
return;
}
$tag = $this->tags->where('name', $this->tag)->first();
if (!$tag) {
$this->dispatch('error', "Tag ({$this->tag}) not found.");
$this->tag = "";
return;
}
$this->webhook = generatTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
$this->get_deployments();
}
public function get_deployments()
{
try {
$resource_ids = $this->applications->pluck('id');
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function redeploy_all()
{
try {
@@ -67,7 +57,7 @@ class Index extends Component
{
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
if ($this->tag) {
$this->updatedTag();
$this->tag_updated();
}
}
public function render()

View File

@@ -21,6 +21,7 @@ class TeamSharedVariablesIndex extends Component
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'is_literal' => $data['is_literal'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);

View File

@@ -511,9 +511,9 @@ class Application extends BaseModel
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
} else {
$newConfigHash .= json_encode($this->environment_variables_preview->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables_preview->get('value')->sort());
}
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
@@ -579,9 +579,9 @@ class Application extends BaseModel
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
$baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid);
$commands = collect([]);
$git_clone_command = "git clone -b {$this->git_branch}";
$git_clone_command = "git clone -b \"{$this->git_branch}\"";
if ($only_checkout) {
$git_clone_command = "git clone --no-checkout -b {$this->git_branch}";
$git_clone_command = "git clone --no-checkout -b \"{$this->git_branch}\"";
}
if ($pull_request_id !== 0) {
$pr_branch_name = "pr-{$pull_request_id}-coolify";
@@ -653,13 +653,13 @@ class Application extends BaseModel
if ($exec_in_docker) {
$commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"),
executeInDocker($deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
]);
} else {
$commands = collect([
"mkdir -p /root/.ssh",
"echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa",
"echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null",
"chmod 600 /root/.ssh/id_rsa",
]);
}
@@ -954,4 +954,9 @@ class Application extends BaseModel
});
return $matches->count() > 0;
}
public function getFilesFromServer(bool $isInit = false)
{
getFilesystemVolumesFromServer($this, $isInit);
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
class EnvironmentVariable extends Model
{
@@ -81,6 +82,44 @@ class EnvironmentVariable extends Model
}
);
}
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (!$this->application_id) {
return true;
}
$found_in_compose = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (!$compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
if ($environments->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
}
return $found_in_compose;
}
);
}
protected function isShared(): Attribute
{
return Attribute::make(

View File

@@ -20,6 +20,26 @@ class LocalFileVolume extends BaseModel
{
return $this->morphTo('resource');
}
public function deleteStorageOnServer()
{
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([
"cd $workdir"
]);
$fs_path = data_get($this, 'fs_path');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
$commands->push("rm -rf $fs_path");
}
ray($commands);
return instant_remote_process($commands, $server);
}
public function saveStorageOnServer()
{
$isService = data_get($this->resource, 'service');
@@ -63,8 +83,7 @@ class LocalFileVolume extends BaseModel
$content = base64_encode($content);
$chmod = $fileVolume->chmod;
$chown = $fileVolume->chown;
ray($content, $path, $chmod, $chown);
$commands->push("echo '$content' | base64 -d > $path");
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
$commands->push("chmod +x $path");
if ($chown) {
$commands->push("chown $chown $path");
@@ -72,7 +91,6 @@ class LocalFileVolume extends BaseModel
if ($chmod) {
$commands->push("chmod $chmod $path");
}
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");

View File

@@ -141,7 +141,7 @@ respond 404
$base64 = base64_encode($conf);
instant_remote_process([
"mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file",
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
$this->reloadCaddy();
return;
@@ -223,7 +223,7 @@ respond 404
instant_remote_process([
"mkdir -p $dynamic_conf_path",
"echo '$base64' | base64 -d > $default_redirect_file",
"echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -349,7 +349,7 @@ respond 404
$base64 = base64_encode($yaml);
instant_remote_process([
"mkdir -p $dynamic_config_path",
"echo '$base64' | base64 -d > $file",
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
if (config('app.env') == 'local') {
@@ -376,7 +376,7 @@ $schema://$host {
}";
$base64 = base64_encode($caddy_file);
instant_remote_process([
"echo '$base64' | base64 -d > $file",
"echo '$base64' | base64 -d | tee $file > /dev/null",
], $this);
$this->reloadCaddy();
}
@@ -481,8 +481,8 @@ $schema://$host {
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
$result = $this->validateConnection();
if ($result) {
['uptime' => $uptime] = $this->validateConnection();
if ($uptime) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
@@ -551,7 +551,7 @@ $schema://$host {
public function loadUnmanagedContainers()
{
if ($this->isFunctional()) {
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
$containers = format_docker_command_output_to_json($containers);
$containers = $containers->map(function ($container) {
$labels = data_get($container, 'Labels');
@@ -748,34 +748,31 @@ $schema://$host {
$server = Server::find($this->id);
if (!$server) {
return false;
return ['uptime' => false, 'error' => 'Server not found.'];
}
if ($server->skipServer()) {
return false;
return ['uptime' => false, 'error' => 'Server skipped.'];
}
// EC2 does not have `uptime` command, lol
$uptime = instant_remote_process(['ls /'], $server, false);
if (!$uptime) {
$server->settings()->update([
'is_reachable' => false,
]);
return false;
} else {
try {
// EC2 does not have `uptime` command, lol
instant_remote_process(['ls /'], $server);
$server->settings()->update([
'is_reachable' => true,
]);
$server->update([
'unreachable_count' => 0,
]);
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return ['uptime' => true, 'error' => null];
} catch (\Throwable $e) {
$server->settings()->update([
'is_reachable' => false,
]);
return ['uptime' => false, 'error' => $e->getMessage()];
}
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return true;
}
public function installDocker()
{
@@ -784,7 +781,7 @@ $schema://$host {
}
public function validateDockerEngine($throwError = false)
{
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
$dockerBinary = instant_remote_process(["command -v docker"], $this, false, no_sudo: true);
if (is_null($dockerBinary)) {
$this->settings->is_usable = false;
$this->settings->save();
@@ -861,4 +858,11 @@ $schema://$host {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
}
}
public function isNonRoot()
{
if ($this->user instanceof Stringable) {
return $this->user->value() !== 'root';
}
return $this->user !== 'root';
}
}

View File

@@ -14,20 +14,20 @@ class Service extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$domains = $this->applications()->get()->pluck('fqdn')->toArray();
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
$domains = implode(',', $domains);
$applicationImages = $this->applications()->get()->pluck('image');
$databaseImages = $this->databases()->get()->pluck('image');
$applicationImages = $this->applications()->get()->pluck('image')->sort();
$databaseImages = $this->databases()->get()->pluck('image')->sort();
$images = $applicationImages->merge($databaseImages);
$images = implode(',', $images->toArray());
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten();
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten();
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten()->sortBy('id');
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
$newConfigHash = $images . $domains . $images . $storages;
$newConfigHash .= json_encode($this->environment_variables()->get('value'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -150,6 +150,76 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
case str($image)?->contains('tolgee'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'TOLGEE_AUTHENTICATION_INITIAL_USERNAME',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_TOLGEE',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Tolgee', $data);
break;
case str($image)?->contains('logto'):
$data = collect([]);
$logto_endpoint = $this->environment_variables()->where('key', 'LOGTO_ENDPOINT')->first();
$logto_admin_endpoint = $this->environment_variables()->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
if ($logto_endpoint) {
$data = $data->merge([
'Endpoint' => [
'key' => data_get($logto_endpoint, 'key'),
'value' => data_get($logto_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
if ($logto_admin_endpoint) {
$data = $data->merge([
'Admin Endpoint' => [
'key' => data_get($logto_admin_endpoint, 'key'),
'value' => data_get($logto_admin_endpoint, 'value'),
'rules' => 'required|url',
],
]);
}
$fields->put('Logto', $data);
break;
case str($image)?->contains('unleash-server'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_UNLEASH')->first();
$data = $data->merge([
'Admin User' => [
'key' => 'SERVICE_USER_UNLEASH',
'value' => 'admin',
'readonly' => true,
'rules' => 'required',
],
]);
if ($admin_password) {
$data = $data->merge([
'Admin Password' => [
'key' => 'SERVICE_PASSWORD_UNLEASH',
'value' => data_get($admin_password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Unleash', $data);
break;
case str($image)?->contains('grafana'):
$data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
@@ -618,7 +688,7 @@ class Service extends BaseModel
$commands[] = "cd $workdir";
$docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$envs = $this->environment_variables()->get();
$commands[] = "rm -f .env || true";
foreach ($envs as $env) {

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class SharedEnvironmentVariable extends Model

View File

@@ -45,7 +45,7 @@ class StandaloneClickhouse extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -207,4 +207,7 @@ class StandaloneClickhouse extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return $this->clickhouse_db;
}
}

View File

@@ -44,7 +44,7 @@ class StandaloneDragonfly extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -167,9 +167,9 @@ class StandaloneDragonfly extends BaseModel
public function get_db_url(bool $useInternal = false): string
{
if ($this->is_public && !$useInternal) {
return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
} else {
return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0";
return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
}
}
@@ -207,4 +207,7 @@ class StandaloneDragonfly extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return '0';
}
}

View File

@@ -44,7 +44,7 @@ class StandaloneKeydb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -208,5 +208,7 @@ class StandaloneKeydb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return '0';
}
}

View File

@@ -46,7 +46,7 @@ class StandaloneMariadb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -208,4 +208,7 @@ class StandaloneMariadb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return $this->mariadb_database;
}
}

View File

@@ -49,7 +49,7 @@ class StandaloneMongodb extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -223,4 +223,7 @@ class StandaloneMongodb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return $this->mongo_db;
}
}

View File

@@ -46,7 +46,7 @@ class StandaloneMysql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -209,4 +209,7 @@ class StandaloneMysql extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return $this->mysql_database;
}
}

View File

@@ -58,7 +58,7 @@ class StandalonePostgresql extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -208,4 +208,7 @@ class StandalonePostgresql extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return $this->postgres_db;
}
}

View File

@@ -41,7 +41,7 @@ class StandaloneRedis extends BaseModel
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf;
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
$newConfigHash = md5($newConfigHash);
$oldConfigHash = data_get($this, 'config_hash');
if ($oldConfigHash === null) {
@@ -204,4 +204,7 @@ class StandaloneRedis extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function database_name() {
return '0';
}
}

View File

@@ -17,11 +17,13 @@ class BackupFailed extends Notification implements ShouldQueue
public $tries = 1;
public string $name;
public string $database_name;
public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database, public $output)
{
$this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency;
}
@@ -36,6 +38,7 @@ class BackupFailed extends Notification implements ShouldQueue
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
$mail->view('emails.backup-failed', [
'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency,
'output' => $this->output,
]);
@@ -44,11 +47,11 @@ class BackupFailed extends Notification implements ShouldQueue
public function toDiscord(): string
{
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
}
public function toTelegram(): array
{
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
return [
"message" => $message,
];

View File

@@ -14,11 +14,13 @@ class BackupSuccess extends Notification implements ShouldQueue
public $tries = 1;
public string $name;
public string $database_name;
public string $frequency;
public function __construct(ScheduledDatabaseBackup $backup, public $database)
{
$this->name = $database->name;
$this->database_name = $database->database_name();
$this->frequency = $backup->frequency;
}
@@ -33,6 +35,7 @@ class BackupSuccess extends Notification implements ShouldQueue
$mail->subject("Coolify: Backup successfully done for {$this->database->name}");
$mail->view('emails.backup-success', [
'name' => $this->name,
'database_name' => $this->database_name,
'frequency' => $this->frequency,
]);
return $mail;
@@ -40,11 +43,11 @@ class BackupSuccess extends Notification implements ShouldQueue
public function toDiscord(): string
{
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
return "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
}
public function toTelegram(): array
{
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
$message = "Coolify: Database backup for {$this->name} (db:{$this->database_name}) with frequency of {$this->frequency} was successful.";
return [
"message" => $message,
];

View File

@@ -20,9 +20,9 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabledNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
\SocialiteProviders\Azure\AzureExtendSocialite::class . '@handle',
],
ProxyStarted::class => [
ProxyStarted::class => [
ProxyStartedNotification::class,
],
];

View File

@@ -34,7 +34,13 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save');
if ($this->server->isNonRoot()) {
if (str($command)->startsWith('docker exec')) {
$command = str($command)->replace('docker exec', 'sudo docker exec');
} else {
$command = parseLineForSudo($command, $this->server);
}
}
$remote_command = generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$output = Str::of($output)->trim();

View File

@@ -0,0 +1,38 @@
<?php
namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
class Datalist extends Component
{
/**
* Create a new component instance.
*/
public function __construct(
public ?string $id = null,
public ?string $name = null,
public ?string $label = null,
public ?string $helper = null,
public bool $required = false,
public string $defaultClass = "input"
) {
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
if (is_null($this->id)) $this->id = new Cuid2(7);
if (is_null($this->name)) $this->name = $this->id;
$this->label = Str::title($this->label);
return view('components.forms.datalist');
}
}

View File

@@ -18,7 +18,10 @@ class Links extends Component
$service->applications()->get()->map(function ($application) {
$type = $application->serviceType();
if ($type) {
$links = generateServiceSpecificFqdns($application, false);
$links = generateServiceSpecificFqdns($application);
$links = $links->map(function ($link) {
return getFqdnWithoutPort($link);
});
$this->links = $this->links->merge($links);
} else {
if ($application->fqdn) {

View File

@@ -9,7 +9,7 @@ use App\Models\StandaloneDocker;
use Illuminate\Support\Collection;
use Spatie\Url\Url;
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false)
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)
{
$application_id = $application->id;
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
@@ -38,6 +38,7 @@ function queue_application_deployment(Application $application, string $deployme
'is_webhook' => $is_webhook,
'restart_only' => $restart_only,
'commit' => $commit,
'rollback' => $rollback,
'git_type' => $git_type,
'only_this_server' => $only_this_server
]);

View File

@@ -32,6 +32,7 @@ const DATABASE_DOCKER_IMAGES = [
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',
'svhd/logto'
];
// Based on /etc/os-release

View File

@@ -169,7 +169,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
}
return $labels;
}
function generateServiceSpecificFqdns(ServiceApplication|Application $resource, $forTraefik = false)
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
$uuid = $resource->uuid;
@@ -182,6 +182,9 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
$environment_variables = $resource->environment_variables;
$type = $resource->serviceType();
}
if (is_null($server) || is_null($type)) {
return collect([]);
}
$variables = collect($environment_variables);
$payload = collect([]);
switch ($type) {
@@ -201,17 +204,31 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
"value" => generateFqdn($server, 'minio-' . $uuid)
]);
}
if ($forTraefik) {
$payload = collect([
$MINIO_BROWSER_REDIRECT_URL->value . ':9001',
$MINIO_SERVER_URL->value . ':9000',
]);
} else {
$payload = collect([
$MINIO_BROWSER_REDIRECT_URL->value,
$MINIO_SERVER_URL->value,
$payload = collect([
$MINIO_BROWSER_REDIRECT_URL->value . ':9001',
$MINIO_SERVER_URL->value . ':9000',
]);
break;
case $type?->contains('logto'):
$LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
$LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) {
return $payload;
}
if (is_null($LOGTO_ENDPOINT?->value)) {
$LOGTO_ENDPOINT?->update([
"value" => generateFqdn($server, 'logto-' . $uuid)
]);
}
if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) {
$LOGTO_ADMIN_ENDPOINT?->update([
"value" => generateFqdn($server, 'logto-admin-' . $uuid)
]);
}
$payload = collect([
$LOGTO_ENDPOINT->value . ':3001',
$LOGTO_ADMIN_ENDPOINT->value . ':3002',
]);
break;
}
return $payload;
@@ -565,7 +582,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
$server = Server::findOrFail($server_id);
$base64_compose = base64_encode($compose);
$output = instant_remote_process([
"echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml",
"echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null",
"docker compose -f /tmp/{$uuid}.yml config",
], $server);
ray($output);
@@ -586,3 +603,9 @@ function escapeEnvVariables($value)
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'");
return str_replace($search, $replace, $value);
}
function escapeDollarSign($value)
{
$search = array('$');
$replace = array('$$');
return str_replace($search, $replace, $value);
}

View File

@@ -32,6 +32,9 @@ function remote_process(
if ($command instanceof Collection) {
$command = $command->toArray();
}
if ($server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
$command_string = implode("\n", $command);
if (auth()->user()) {
$teams = auth()->user()->teams->pluck('id');
@@ -133,17 +136,17 @@ function generateSshCommand(Server $server, string $command)
$timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval');
$muxPersistTime = config('constants.ssh.mux_persist_time');
$ssh_command = "timeout $timeout ssh ";
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
$ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ";
}
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
}
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "-i {$privateKeyLocation} "
@@ -159,17 +162,19 @@ function generateSshCommand(Server $server, string $command)
. $command . PHP_EOL
. $delimiter;
// ray($ssh_command);
// ray($delimiter);
return $ssh_command;
}
function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
{
$timeout = config('constants.ssh.command_timeout');
if ($command instanceof Collection) {
$command = $command->toArray();
}
if ($server->isNonRoot() && !$no_sudo) {
$command = parseCommandsByLineForSudo(collect($command), $server);
}
$command_string = implode("\n", $command);
$ssh_command = generateSshCommand($server, $command_string);
$ssh_command = generateSshCommand($server, $command_string, $no_sudo);
$process = Process::timeout($timeout)->run($ssh_command);
$output = trim($process->output());
$exitCode = $process->exitCode();

View File

@@ -1,7 +1,7 @@
<?php
use App\Models\Application;
use App\Models\EnvironmentVariable;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Str;
@@ -21,16 +21,20 @@ function replaceVariables($variable)
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
}
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
{
// TODO: make this async
try {
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
if ($oneService->getMorphClass() === 'App\Models\Application') {
$workdir = $oneService->workdir();
$server = $oneService->destination->server;
} else{
$workdir = $oneService->service->workdir();
$server = $oneService->service->server;
}
$fileVolumes = $oneService->fileStorages()->get();
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",
"cd "
"cd $workdir"
]);
instant_remote_process($commands, $server);
foreach ($fileVolumes as $fileVolume) {
@@ -67,7 +71,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
$dir = Str::of($fileLocation)->dirname();
instant_remote_process([
"mkdir -p $dir",
"echo '$content' | base64 -d > $fileLocation"
"echo '$content' | base64 -d | tee $fileLocation"
], $server);
} else if ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
$fileVolume->content = null;

View File

@@ -29,10 +29,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Process\Pool;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
@@ -656,6 +659,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
@@ -978,12 +984,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}")) {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
}
if ($resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
if ($env) {
$env_url = Url::fromString($savedService->fqdn);
$env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) {
@@ -1045,6 +1056,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$fqdns_exploded = str($savedService->fqdn)->explode(',');
if ($fqdns_exploded->count() > 1) {
continue;
}
$env = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
@@ -1109,10 +1124,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
}
}
// Add labels to the service
if ($savedService->serviceType()) {
$fqdns = generateServiceSpecificFqdns($savedService, forTraefik: true);
$fqdns = generateServiceSpecificFqdns($savedService);
} else {
$fqdns = collect(data_get($savedService, 'fqdns'))->filter();
}
@@ -1254,6 +1268,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels->push("$removedLabelName=$removedLabel");
}
}
if ($serviceLabels->count() > 0) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName";
if (count($serviceVolumes) > 0) {
@@ -1412,6 +1431,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
foreach ($definedNetwork as $key => $network) {
$networks->put($network, null);
}
if (data_get($resource, 'settings.connect_to_docker_network')) {
$network = $resource->destination->network;
$networks->put($network, null);
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
}
data_set($service, 'networks', $networks->toArray());
// Get variables from the service
foreach ($serviceVariables as $variableName => $variable) {
@@ -1482,7 +1509,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
ray($command, $generatedValue);
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
@@ -1567,14 +1593,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
// Add labels to the service
if ($resource->serviceType()) {
$fqdns = generateServiceSpecificFqdns($resource, forTraefik: true);
$fqdns = generateServiceSpecificFqdns($resource);
} else {
$domains = collect(json_decode($resource->docker_compose_domains)) ?? [];
if ($domains) {
$fqdns = data_get($domains, "$serviceName.domain");
if ($fqdns) {
$fqdns = str($fqdns)->explode(',');
$uuid = new Cuid2(7);
if ($pull_request_id !== 0) {
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
@@ -1593,13 +1618,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
});
}
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $uuid,
uuid: $resource->uuid,
domains: $fqdns,
serviceLabels: $serviceLabels
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $resource->destination->network,
uuid: $uuid,
uuid: $resource->uuid,
domains: $fqdns,
serviceLabels: $serviceLabels
));
@@ -1939,3 +1964,91 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
}
}
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (!str($line)->startsWith('cd') && !str($line)->startsWith('command') && !str($line)->startsWith('echo') && !str($line)->startsWith('true')) {
return "sudo $line";
}
return $line;
});
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p');
}
return $line;
});
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
$line = $line->replace('$(', '$(sudo ');
}
if (str($line)->contains('||')) {
$line = $line->replace('||', '|| sudo');
}
if (str($line)->contains('&&')) {
$line = $line->replace('&&', '&& sudo');
}
if (str($line)->contains(' | ')) {
$line = $line->replace(' | ', ' | sudo ');
}
return $line->value();
});
return $commands->toArray();
}
function parseLineForSudo(string $command, Server $server): string
{
if (!str($command)->startSwith('cd') && !str($command)->startSwith('command')) {
$command = "sudo $command";
}
if (Str::startsWith($command, 'sudo mkdir -p')) {
$command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p');
}
if (str($command)->contains('$(') || str($command)->contains('`')) {
$command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value();
}
if (str($command)->contains('||')) {
$command = str($command)->replace('||', '|| sudo ')->value();
}
if (str($command)->contains('&&')) {
$command = str($command)->replace('&&', '&& sudo ')->value();
}
return $command;
}
function get_public_ips()
{
try {
echo "Refreshing public ips!\n";
$settings = InstanceSettings::get();
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
});
$ipv4 = $first->output();
if ($ipv4) {
$ipv4 = trim($ipv4);
$validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
if ($validate_ipv4 == false) {
echo "Invalid ipv4: $ipv4\n";
return;
}
$settings->update(['public_ipv4' => $ipv4]);
}
$ipv6 = $second->output();
if ($ipv6) {
$ipv6 = trim($ipv6);
$validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
if ($validate_ipv6 == false) {
echo "Invalid ipv6: $ipv6\n";
return;
}
$settings->update(['public_ipv6' => $ipv6]);
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
}

View File

@@ -141,8 +141,8 @@ return [
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
'driver' => 'cache',
'store' => 'redis',
],
/*

View File

@@ -5,6 +5,7 @@ return [
'contact' => 'https://coolify.io/docs/contact',
],
'ssh' => [
'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', "1m"),
'connection_timeout' => 10,
'server_interval' => 20,
'command_timeout' => 7200,

View File

@@ -183,9 +183,7 @@ return [
's6' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
// 'autoScalingStrategy' => 'time',
// 'maxProcesses' => 1,
'balance' => env('HORIZON_BALANCE', 'auto'),
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,

View File

@@ -146,4 +146,5 @@ return [
*/
'pagination_theme' => 'tailwind',
'lazy_placeholder' => 'components.page-loading',
];

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.258',
'release' => '4.0.0-beta.267',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.258';
return '4.0.0-beta.267';

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->boolean('is_literal')->default(false);
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->boolean('is_literal')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('is_literal');
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropColumn('is_literal');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('services', function (Blueprint $table) {
$table->string('service_type')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('services', function (Blueprint $table) {
$table->dropColumn('service_type');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->boolean('rollback')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('rollback');
});
}
};

View File

@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('connect_to_docker_network')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('connect_to_docker_network');
});
}
};

View File

@@ -177,27 +177,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
}
try {
$settings = InstanceSettings::get();
if (is_null($settings->public_ipv4)) {
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
if ($ipv4) {
$ipv4 = trim($ipv4);
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
$settings->update(['public_ipv4' => $ipv4]);
}
}
if (is_null($settings->public_ipv6)) {
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
if ($ipv6) {
$ipv6 = trim($ipv6);
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
$settings->update(['public_ipv6' => $ipv6]);
}
}
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
get_public_ips();
$oauth_settings_seeder = new OauthSettingSeeder();
$oauth_settings_seeder->run();

View File

@@ -29,6 +29,7 @@ services:
- QUEUE_CONNECTION
- REDIS_HOST
- REDIS_PASSWORD
- HORIZON_BALANCE
- HORIZON_MAX_PROCESSES
- HORIZON_BALANCE_MAX_SHIFT
- HORIZON_BALANCE_COOLDOWN
@@ -47,6 +48,7 @@ services:
- PUSHER_APP_SECRET
- AUTOUPDATE
- SELF_HOSTED
- SSH_MUX_PERSIST_TIME
- FEEDBACK_DISCORD_WEBHOOK
- WAITLIST
- SUBSCRIPTION_PROVIDER
@@ -93,7 +95,7 @@ services:
expose:
- "${APP_PORT:-8000}"
healthcheck:
test: curl --fail http://localhost:80/api/health || exit 1
test: curl --fail http://127.0.0.1:80/api/health || exit 1
interval: 5s
retries: 10
timeout: 2s
@@ -140,7 +142,7 @@ services:
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
healthcheck:
test: wget -qO- http://localhost:6001/ready || exit 1
test: wget -qO- http://127.0.0.1:6001/ready || exit 1
interval: 5s
retries: 10
timeout: 2s

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
public/svgs/authentik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path d="M111.108,9.531l-0.006,-0.011c-0.435,-0.618 -1.151,-0.981 -1.906,-0.966c-13.039,0.734 -34.043,8.376 -34.929,8.701c-0.512,0.188 -0.932,0.566 -1.174,1.054l-2.96,5.911l-2.181,-2.183c-0.658,-0.664 -1.681,-0.819 -2.508,-0.381c-2.497,1.305 -15.121,7.998 -17.807,11.256c-1.559,1.885 -2.764,6.031 -3.581,9.685l-1.427,-2.846c-0.295,-0.595 -0.851,-1.018 -1.503,-1.144c-0.652,-0.135 -1.33,0.048 -1.826,0.492c-0.755,0.663 -16.512,14.842 -13.232,32.52c12.607,-17.973 27.758,-33.089 52.542,-45.254c0.295,-0.146 0.619,-0.221 0.948,-0.221c1.175,-0 2.143,0.967 2.143,2.144c-0,0.814 -0.465,1.561 -1.195,1.922l-0.007,-0c-1.442,0.713 -2.842,1.433 -4.216,2.162c-0.35,0.184 -0.693,0.372 -1.038,0.558c-1.055,0.571 -2.093,1.148 -3.113,1.73c-0.333,0.191 -0.662,0.378 -0.99,0.569c-2.629,1.532 -5.141,3.108 -7.534,4.727c-0.197,0.132 -0.39,0.268 -0.59,0.406c-1.042,0.713 -2.064,1.436 -3.067,2.166c-0.179,0.129 -0.35,0.257 -0.526,0.387c-23.388,17.253 -35.764,39.115 -50.59,65.336c-0.182,0.322 -0.279,0.685 -0.279,1.055c0,1.174 0.967,2.14 2.141,2.14l0.005,0c0.772,0.003 1.487,-0.413 1.865,-1.085c5.905,-10.442 11.416,-20.177 17.392,-29.187c2.434,2.967 6.13,4.62 9.964,4.46c16.21,-0 42.981,-23.712 45.677,-31.349c0.081,-0.23 0.123,-0.472 0.123,-0.715c0,-0.98 -0.672,-1.841 -1.623,-2.077l-7.145,-1.789l15.555,-2.592c0.677,-0.111 1.261,-0.543 1.564,-1.16l17.152,-34.3c0.328,-0.679 0.283,-1.482 -0.118,-2.121Z" style="fill:#fff;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 101.4 33.5">
<defs>
<style>
.a {
fill: #fff;
}
.b {
fill: #f48120;
}
.c {
fill: #faad3f;
}
.d {
fill: #404041;
}
</style>
</defs>
<title>Cloudflare logo</title>
<path class="a" d="M94.7,10.6,89.1,9.3l-1-.4-25.7.2V21.5l32.3.1Z"/>
<path class="b" d="M84.2,20.4a2.85546,2.85546,0,0,0-.3-2.6,3.09428,3.09428,0,0,0-2.1-1.1l-17.4-.2c-.1,0-.2-.1-.3-.1a.1875.1875,0,0,1,0-.3c.1-.2.2-.3.4-.3L82,15.6a6.29223,6.29223,0,0,0,5.1-3.8l1-2.6c0-.1.1-.2,0-.3A11.39646,11.39646,0,0,0,66.2,7.7a5.45941,5.45941,0,0,0-3.6-1A5.20936,5.20936,0,0,0,58,11.3a5.46262,5.46262,0,0,0,.1,1.8A7.30177,7.30177,0,0,0,51,20.4a4.102,4.102,0,0,0,.1,1.1.3193.3193,0,0,0,.3.3H83.5c.2,0,.4-.1.4-.3Z"/>
<path class="c" d="M89.7,9.2h-.5c-.1,0-.2.1-.3.2l-.7,2.4a2.85546,2.85546,0,0,0,.3,2.6,3.09428,3.09428,0,0,0,2.1,1.1l3.7.2c.1,0,.2.1.3.1a.1875.1875,0,0,1,0,.3c-.1.2-.2.3-.4.3l-3.8.2a6.29223,6.29223,0,0,0-5.1,3.8l-.2.9c-.1.1,0,.3.2.3H98.5a.26517.26517,0,0,0,.3-.3,10.87184,10.87184,0,0,0,.4-2.6,9.56045,9.56045,0,0,0-9.5-9.5"/>
<path class="d" d="M100.5,27.2a.9.9,0,1,1,.9-.9.89626.89626,0,0,1-.9.9m0-1.6a.7.7,0,1,0,.7.7.68354.68354,0,0,0-.7-.7m.4,1.2h-.2l-.2-.3h-.2v.3h-.2v-.9h.5a.26517.26517,0,0,1,.3.3c0,.1-.1.2-.2.3l.2.3Zm-.3-.5c.1,0,.1,0,.1-.1a.09794.09794,0,0,0-.1-.1h-.3v.3h.3Zm-89.7-.9h2.2v6h3.8v1.9h-6Zm8.3,3.9a4.10491,4.10491,0,0,1,4.3-4.1,4.02,4.02,0,0,1,4.2,4.1,4.10491,4.10491,0,0,1-4.3,4.1,4.07888,4.07888,0,0,1-4.2-4.1m6.3,0a2.05565,2.05565,0,0,0-2-2.2,2.1025,2.1025,0,0,0,0,4.2c1.2.2,2-.8,2-2m4.9.5V25.4h2.2v4.4c0,1.1.6,1.7,1.5,1.7a1.39926,1.39926,0,0,0,1.5-1.6V25.4h2.2v4.4c0,2.6-1.5,3.7-3.7,3.7-2.3-.1-3.7-1.2-3.7-3.7m10.7-4.4h3.1c2.8,0,4.5,1.6,4.5,3.9s-1.7,4-4.5,4h-3V25.4Zm3.1,5.9a2.00909,2.00909,0,1,0,0-4h-.9v4Zm7.6-5.9h6.3v1.9H54v1.3h3.7v1.8H54v2.9H51.8Zm9.4,0h2.2v6h3.8v1.9h-6Zm11.7-.1h2.2l3.4,8H76.1l-.6-1.4H72.4l-.6,1.4H69.5Zm2,4.9L74,28l-.9,2.2Zm6.4-4.8H85a3.41818,3.41818,0,0,1,2.6.9,2.62373,2.62373,0,0,1-.9,4.2l1.9,2.8H86.1l-1.6-2.4h-1v2.4H81.3Zm3.6,3.8c.7,0,1.2-.4,1.2-.9,0-.6-.5-.9-1.2-.9H83.5v1.9h1.4Zm6.5-3.8h6.4v1.8H93.6v1.2h3.8v1.8H93.6v1.2h4.3v1.9H91.4ZM6.1,30.3a1.97548,1.97548,0,0,1-1.8,1.2,2.1025,2.1025,0,0,1,0-4.2,2.0977,2.0977,0,0,1,1.9,1.3H8.5a4.13459,4.13459,0,0,0-4.2-3.3A4.1651,4.1651,0,0,0,0,29.4a4.07888,4.07888,0,0,0,4.2,4.1,4.31812,4.31812,0,0,0,4.2-3.2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,14 @@
<svg viewBox="0 0 154 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 7.49503C2 6.1159 2 5.42633 2.26742 4.87131C2.50309 4.38219 2.8821 3.96763 3.35931 3.67702C3.90082 3.34726 4.62076 3.24937 6.06063 3.05361L26.5958 0.261598C28.4598 0.00816784 29.3918 -0.118547 30.1161 0.149002C30.7518 0.383867 31.2824 0.81992 31.6179 1.38326C32 2.025 32 2.91768 32 4.70302V43.297C32 45.0823 32 45.975 31.6179 46.6167C31.2824 47.1801 30.7518 47.6161 30.1161 47.851C29.3918 48.1185 28.4598 47.9918 26.5959 47.7384H26.5959H26.5958L6.06063 44.9464L6.06057 44.9464C4.62074 44.7506 3.90081 44.6527 3.35931 44.323C2.8821 44.0324 2.50309 43.6178 2.26742 43.1287C2 42.5737 2 41.8841 2 40.505V7.49503ZM24 20.5078C24 20.0879 24 19.8779 24.0705 19.7119C24.1326 19.5657 24.2323 19.4436 24.3567 19.3611C24.4979 19.2674 24.6847 19.2484 25.0583 19.2104L26.731 19.0402C27.1712 18.9954 27.3912 18.973 27.5611 19.0552C27.7104 19.1273 27.8338 19.2535 27.9115 19.4134C28 19.5954 28 19.8428 28 20.3376V27.6624C28 28.1572 28 28.4046 27.9115 28.5866C27.8338 28.7465 27.7104 28.8727 27.5611 28.9448C27.3912 29.027 27.1712 29.0046 26.731 28.9598L25.0583 28.7896C24.6847 28.7516 24.4979 28.7326 24.3567 28.6389C24.2323 28.5564 24.1326 28.4343 24.0705 28.2881C24 28.1221 24 27.9121 24 27.4922V20.5078ZM37.6444 5H35V44H37.6444C39.169 44 39.9313 44 40.5136 43.7024C41.0259 43.4407 41.4423 43.0231 41.7033 42.5094C42 41.9254 42 41.1609 42 39.632V9.368C42 7.83906 42 7.07458 41.7033 6.49061C41.4423 5.97692 41.0259 5.55929 40.5136 5.29755C39.9313 5 39.169 5 37.6444 5Z" fill="url(#paint0_linear_865_5913)"/>
<path d="M85.5299 36.8007C83.4751 36.8007 81.7004 36.3737 80.206 35.5198C78.7116 34.6658 77.5641 33.4783 76.7635 31.9572C75.963 30.4094 75.5627 28.6215 75.5627 26.5933C75.5627 24.5118 75.9763 22.7105 76.8036 21.1894C77.6308 19.6683 78.7917 18.4808 80.2861 17.6268C81.8072 16.7729 83.5818 16.3459 85.6099 16.3459C87.6648 16.3459 89.426 16.7729 90.8938 17.6268C92.3882 18.4808 93.549 19.6683 94.3763 21.1894C95.2036 22.7105 95.6172 24.4985 95.6172 26.5533C95.6172 28.5814 95.1902 30.3694 94.3363 31.9172C93.509 33.465 92.3482 34.6658 90.8537 35.5198C89.3593 36.3471 87.5847 36.774 85.5299 36.8007ZM85.5699 32.9179C86.6907 32.9179 87.6514 32.6377 88.452 32.0773C89.2526 31.5169 89.8664 30.7563 90.2933 29.7956C90.747 28.8349 90.9738 27.7542 90.9738 26.5533C90.9738 25.3257 90.7603 24.2316 90.3334 23.2709C89.9064 22.3102 89.2926 21.563 88.492 21.0293C87.6914 20.4689 86.7307 20.1887 85.6099 20.1887C84.5425 20.1887 83.5951 20.4689 82.7679 21.0293C81.9406 21.5897 81.3001 22.3502 80.8465 23.3109C80.4195 24.2716 80.1927 25.3524 80.166 26.5533C80.166 27.7808 80.3795 28.875 80.8065 29.8357C81.2334 30.7964 81.8606 31.5569 82.6878 32.1173C83.5151 32.651 84.4758 32.9179 85.5699 32.9179Z" fill="white"/>
<path d="M116.582 17.0751C116.582 16.9598 116.488 16.8663 116.373 16.8663H112.268C112.152 16.8663 112.059 16.9598 112.059 17.0751V37.5613C112.059 38.9222 111.552 39.7675 110.538 40.5147C109.55 41.2619 108.44 41.5062 106.865 41.5329C105.771 41.5062 104.744 41.4088 103.837 41.1152C102.986 40.8649 102.055 40.4971 101.084 40.012C100.978 39.9592 100.849 40.0031 100.799 40.1101L99.3229 43.2556C99.2803 43.3465 99.3081 43.4551 99.3911 43.5116C99.8414 43.8181 100.368 44.0919 100.971 44.333C101.638 44.5998 102.318 44.8133 103.012 44.9734C103.733 45.1602 104.477 45.3161 105.118 45.3961C105.785 45.4762 106.415 45.487 106.815 45.487C108.843 45.487 110.578 45.1668 112.019 44.5263C113.486 43.8858 114.607 42.9785 115.381 41.8043C116.182 40.6302 116.582 39.2291 116.582 37.6013V17.0751ZM113.314 30.3339C113.253 30.1892 113.059 30.1611 112.956 30.2796C112.171 31.183 111.373 31.823 110.618 32.2374C109.817 32.6911 108.896 32.9179 107.856 32.9179C106.788 32.9179 105.854 32.651 105.054 32.1173C104.28 31.5569 103.693 30.7964 103.292 29.8357C102.892 28.875 102.692 27.7675 102.692 26.5133C102.692 25.3124 102.892 24.2449 103.292 23.3109C103.693 22.3502 104.28 21.5897 105.054 21.0293C105.854 20.4689 106.802 20.1887 107.896 20.1887C108.91 20.1887 109.71 20.4022 110.538 20.8291C111.31 21.2153 112.132 22.0971 112.839 23.0181C112.938 23.1464 113.139 23.1225 113.202 22.9736L114.338 20.2934C114.366 20.2281 114.359 20.1533 114.319 20.0952C112.885 18.0383 110.461 16.3459 107.7 16.3459C105.699 16.3459 104.12 16.7729 102.652 17.6268C101.184 18.4808 100.05 19.6683 99.2494 21.1894C98.4755 22.7105 98.0886 24.4851 98.0886 26.5133C98.0886 28.5948 98.4755 30.4094 99.2494 31.9572C100.023 33.4783 101.131 34.6658 102.572 35.5198C104.04 36.3737 105.774 36.8007 107.776 36.8007C108.523 36.8007 109.297 36.6539 110.097 36.3604C110.925 36.0935 111.725 35.6932 112.499 35.1595C113.239 34.6489 113.894 34.0405 114.464 33.3345C114.511 33.2753 114.522 33.1947 114.493 33.1247L113.314 30.3339Z" fill="white"/>
<path d="M132.061 17.2352C132.061 17.1199 131.968 17.0264 131.852 17.0264H127.386C127.271 17.0264 127.178 16.9329 127.178 16.8176V11.3577C127.178 11.2424 127.084 11.1489 126.969 11.1489H122.903C122.788 11.1489 122.694 11.2424 122.694 11.3577V16.8176C122.694 16.9329 122.601 17.0264 122.486 17.0264H119.766C119.65 17.0264 119.556 17.1212 119.557 17.2374L119.593 20.6225C119.594 20.737 119.687 20.8291 119.801 20.8291H122.486C122.601 20.8291 122.694 20.9226 122.694 21.038V30.4761C122.694 31.8905 122.975 33.0914 123.535 34.0787C124.122 35.0394 124.923 35.7733 125.937 36.2803C126.951 36.7874 128.178 37.0409 129.619 37.0409C130.02 37.0409 130.447 36.9875 130.9 36.8808C131.381 36.774 131.845 36.6617 132.245 36.5015C132.602 36.3675 133.299 36.0182 133.654 35.8124C133.748 35.7576 133.777 35.6383 133.724 35.5431L132.072 32.631C132.02 32.5386 131.907 32.5 131.808 32.5378C131.541 32.6396 131.265 32.7234 130.98 32.7891C130.633 32.8692 130.286 32.9092 129.94 32.9092C129.006 32.9092 128.312 32.8112 127.858 32.1974C127.404 31.5569 127.178 30.743 127.178 29.7556V21.038C127.178 20.9226 127.271 20.8291 127.386 20.8291H131.852C131.968 20.8291 132.061 20.7356 132.061 20.6203V17.2352Z" fill="white"/>
<path d="M143.913 36.7907C141.858 36.7907 140.083 36.3638 138.589 35.5098C137.094 34.6559 135.947 33.4683 135.146 31.9472C134.346 30.3994 133.945 28.6115 133.945 26.5833C133.945 24.5018 134.359 22.7005 135.186 21.1794C136.014 19.6583 137.174 18.4708 138.669 17.6168C140.19 16.7629 141.965 16.3359 143.993 16.3359C146.048 16.3359 147.809 16.7629 149.277 17.6168C150.771 18.4708 151.932 19.6583 152.759 21.1794C153.586 22.7005 154 24.4885 154 26.5433C154 28.5715 153.573 30.3594 152.719 31.9072C151.892 33.455 150.731 34.6559 149.237 35.5098C147.742 36.3371 145.968 36.7641 143.913 36.7907ZM143.953 32.9079C145.074 32.9079 146.034 32.6277 146.835 32.0673C147.635 31.5069 148.249 30.7464 148.676 29.7857C149.13 28.825 149.357 27.7442 149.357 26.5433C149.357 25.3158 149.143 24.2216 148.716 23.2609C148.289 22.3002 147.675 21.553 146.875 21.0193C146.074 20.4589 145.114 20.1787 143.993 20.1787C142.925 20.1787 141.978 20.4589 141.151 21.0193C140.323 21.5797 139.683 22.3403 139.229 23.301C138.802 24.2617 138.575 25.3424 138.549 26.5433C138.549 27.7709 138.762 28.865 139.189 29.8257C139.616 30.7864 140.243 31.5469 141.071 32.1074C141.898 32.6411 142.859 32.9079 143.953 32.9079Z" fill="white"/>
<path d="M73.6634 32.7578C73.6634 32.6425 73.5699 32.549 73.4546 32.549H62.652C62.5367 32.549 62.4432 32.4555 62.4432 32.3401V11.3136C62.4432 11.1982 62.3497 11.1047 62.2344 11.1047H58.2088C58.0935 11.1047 58 11.1982 58 11.3136V36.5919C58 36.7072 58.0935 36.8007 58.2088 36.8007H73.4546C73.5699 36.8007 73.6634 36.7072 73.6634 36.5919V32.7578Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_865_5913" x1="-10.381" y1="33.1953" x2="38.8896" y2="13.0537" gradientUnits="userSpaceOnUse">
<stop stop-color="#4B2EFB"/>
<stop offset="1" stop-color="#E65FFC"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

18
public/svgs/rxresume.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

1
public/svgs/unleash.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="bg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 251.43 251.03"><defs><style>.cls-1{fill:#1a4049;}.cls-2{fill:#fff;}.cls-3{fill:#817afe;}</style></defs><circle class="cls-1" cx="125.71" cy="125.31" r="80"/><polygon class="cls-2" points="137.14 91.03 137.14 113.88 137.14 136.74 160 136.74 160 113.88 160 91.03 137.14 91.03"/><polygon class="cls-2" points="114.29 113.88 114.29 91.03 91.43 91.03 91.43 113.88 91.43 136.74 91.43 159.6 114.29 159.6 137.14 159.6 137.14 136.74 114.29 136.74 114.29 113.88"/><rect class="cls-3" x="137.14" y="136.74" width="22.86" height="22.86"/></svg>

After

Width:  |  Height:  |  Size: 593 B

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