Compare commits

...

568 Commits

Author SHA1 Message Date
Andras Bacsai
436a1d945f Merge pull request #3663 from coollabsio/next
v4.0.0-beta.350
2024-10-03 10:09:05 +02:00
Andras Bacsai
a6a3abc273 chore: Update soketi service image to version 1.0.3 2024-10-03 10:05:54 +02:00
Andras Bacsai
c4e702f096 fix: able to select root permission easier 2024-10-03 09:57:37 +02:00
Andras Bacsai
e91939a4ea refactor: Remove unnecessary watch command from soketi service entrypoint 2024-10-02 21:24:09 +02:00
Andras Bacsai
ceccd093d2 refactor: Improve socket reconnection interval in terminal.js 2024-10-02 21:24:05 +02:00
Andras Bacsai
66bb4e0fc1 refactor: Remove inactivity timer in terminal-server.js 2024-10-02 21:23:59 +02:00
Andras Bacsai
dd3ff38df7 refactor: Encode delimiter in SshMultiplexingHelper 2024-10-02 21:23:46 +02:00
Andras Bacsai
69553ec314 refactor: Improve popup component styling and button behavior 2024-10-02 18:26:51 +02:00
Andras Bacsai
7bb1bf0ae3 refactor: Improve parsing of commands for sudo in parseCommandsByLineForSudo 2024-10-02 18:26:40 +02:00
Andras Bacsai
a1a8f1336a refactor: Fix indentation in modal-confirmation.blade.php 2024-10-02 17:35:48 +02:00
Andras Bacsai
207fe1d709 wtf wtf wtf 2024-10-02 17:27:02 +02:00
Andras Bacsai
059535a676 chore: Remove commented out code for uploading to S3 in DatabaseBackupJob 2024-10-02 16:43:01 +02:00
Andras Bacsai
765a74ca4f handle errors in databasebackupjob 2024-10-02 15:33:14 +02:00
Andras Bacsai
e03e4f2e91 refactor: Improve SSH command generation in Terminal.php and terminal-server.js 2024-10-02 15:16:55 +02:00
Andras Bacsai
0ab432d5e6 chore: Remove unnecessary command from SshMultiplexingHelper 2024-10-02 14:54:48 +02:00
Andras Bacsai
6f25b548c7 fix: realtime watch in development mode 2024-10-02 14:18:52 +02:00
Andras Bacsai
d55e4bf381 feat: Handle HTTPS domain in ConfigureCloudflareTunnels 2024-10-02 13:36:25 +02:00
Andras Bacsai
ec216254b5 chore: Update modal input in server form to prevent closing on outside click 2024-10-02 13:36:09 +02:00
Andras Bacsai
fbb36bfe8e revert modal-confirmation in dev 2024-10-02 12:01:12 +02:00
Andras Bacsai
dd782e75f5 fix: local dev s3 uploads
fix: hetzner s3 uploads (mc alias instead of mc host)
2024-10-02 11:45:30 +02:00
Andras Bacsai
2be2f0ac79 feat: support Hetzner S3 2024-10-02 10:25:45 +02:00
Andras Bacsai
97943db5f4 chore: Add missing import for Attribute class in ApplicationDeploymentQueue model 2024-10-02 09:21:54 +02:00
Andras Bacsai
bbd2748ad7 chore: Update command signature and description for cleanup application deployment queue 2024-10-02 09:21:50 +02:00
Andras Bacsai
a530804a71 feat: Add command to check application deployment queue 2024-10-02 09:21:28 +02:00
Andras Bacsai
8ca8ab82b0 refactor: Remove deployment queue when deleting an application 2024-10-02 09:20:49 +02:00
Andras Bacsai
024ad8e943 fix: cleanup stucked applicationdeploymentqueue 2024-10-02 09:20:08 +02:00
Andras Bacsai
4d86b556a4 fix: ipv6 scp should use -6 flag 2024-10-02 08:15:03 +02:00
Andras Bacsai
73e38ff951 chore: Update version numbers to 4.0.0-beta.350 in configuration files 2024-10-02 08:13:49 +02:00
Andras Bacsai
5354561c39 Merge pull request #3659 from coollabsio/next
v4.0.0-beta.349
2024-10-01 21:36:50 +02:00
Andras Bacsai
223cd37031 fix: remove autofocuses 2024-10-01 21:36:16 +02:00
Andras Bacsai
93a0725a4e Merge pull request #3613 from nfnot/main
refactor: Update search input placeholder in resource index view
2024-10-01 21:32:21 +02:00
Andras Bacsai
4d1d4598b6 chore: Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 2024-10-01 21:31:54 +02:00
Norman
888e96448c Merge branch 'main' into main 2024-10-01 13:33:02 +03:00
Andras Bacsai
0eea31544e Merge pull request #3636 from coollabsio/next
v4.0.0-beta.348
2024-10-01 12:04:39 +02:00
Andras Bacsai
8065da9fa0 Refactor input fields to include autocomplete="off" 2024-10-01 11:55:01 +02:00
Andras Bacsai
5cd81fe255 wip: server storage check 2024-10-01 11:52:36 +02:00
Andras Bacsai
b00828d4aa chore: Refactor instanceSettings() function and improve code readability 2024-10-01 11:36:37 +02:00
Andras Bacsai
5a770f9ddb fix: Handle deletion of 'hello' in confirmation modal for dev environment 2024-10-01 11:16:16 +02:00
Andras Bacsai
903d84e0dd fix: confirmation text could be empty, then skip that part
fix: confirmation modal should not be closed by clicking away
2024-10-01 10:53:20 +02:00
Andras Bacsai
16ccc87fbe chore: Add "Not Usable" indicator for storage items 2024-10-01 10:45:03 +02:00
Andras Bacsai
e4108863a8 chore: Remove unnecessary code in DatabaseBackupJob.php 2024-10-01 10:43:04 +02:00
Andras Bacsai
83549965ca Refactor instanceSettings() function for improved code readability 2024-10-01 10:37:40 +02:00
Andras Bacsai
4d9dcfb84c chore: Update Coolify Realtime image to version 1.0.2 2024-10-01 10:34:11 +02:00
Andras Bacsai
4db50bd025 chore: Refactor instanceSettings() function to improve code readability 2024-10-01 10:33:56 +02:00
Andras Bacsai
817bd9c35d chore: Update Coolify Realtime workflow to only trigger on the main branch 2024-10-01 10:09:38 +02:00
Andras Bacsai
2e10d670be Merge pull request #3647 from derpoho/next
fixes coollabsio#3645, incorrect adding of sudo if non-root user
2024-10-01 10:00:48 +02:00
Andras Bacsai
cec0d7d1f7 Merge pull request #3651 from LEstradioto/fix-enhance-terminal-2
Fix Terminal: no more exit fallback
2024-10-01 09:52:38 +02:00
Andras Bacsai
1f45532477 fix: mixpost 2024-10-01 09:35:54 +02:00
Andras Bacsai
bcc92e1f32 fix: in dev mode do not ask confirmation on delete 2024-10-01 09:31:01 +02:00
Andras Bacsai
31cf3294bf Update UUID description in API controller and OpenAPI specification 2024-10-01 09:04:01 +02:00
Andras Bacsai
26307334a4 feat: Add query parameters for deleting configurations, volumes, docker cleanup, and connected networks 2024-10-01 09:02:29 +02:00
Andras Bacsai
9212f3b24c feat: Update resource deletion job to allow configurable options through API 2024-10-01 09:02:16 +02:00
Andras Bacsai
31b2196ad9 Merge pull request #3650 from ImBIOS/patch-1
chore: fix docs link in running state
2024-10-01 07:40:59 +02:00
Luan Estradioto
2c38af1df0 - fixes: no more exit fallback to server 2024-10-01 01:42:09 -03:00
Imamuzzaki Abu Salam
89e84c5b48 chore: fix docs link in running state 2024-10-01 10:16:16 +07:00
Marcus Pohorely
39d2fdc08b Merge branch 'main' into next 2024-09-30 23:24:42 +02:00
Marcus Pohorely
ac9f817b9f fixes #3645, incorrect adding of sudo if non-root user 2024-09-30 23:21:58 +02:00
Andras Bacsai
852e881805 Merge pull request #2351 from AlejandroAkbal/main
feat: add Mixpost template
2024-09-30 16:35:55 +02:00
Andras Bacsai
001dec6604 chore: Update Mailpit logo to use SVG format 2024-09-30 16:31:36 +02:00
Andras Bacsai
cf9c62cbf0 Merge pull request #3202 from FranckKe/add_mailpit_template
feat: add Mailpit template
2024-09-30 16:16:05 +02:00
Andras Bacsai
99a4f721f1 Merge pull request #2904 from LEstradioto/add-storage-link-to-dev-environment
add storage:link to dev environment
2024-09-30 15:48:10 +02:00
Andras Bacsai
505be9dc97 add debugbar config 2024-09-30 15:44:07 +02:00
Andras Bacsai
cfec5b3a70 feat: Add support for use_build_server in API endpoints for creating/updating applications 2024-09-30 14:18:41 +02:00
Andras Bacsai
95483f1464 Merge pull request #3615 from liberocks/feature/api-use-build-server
feat: allow specify use_build_server when creating/updating an application through API
2024-09-30 14:17:59 +02:00
Andras Bacsai
987b90ead2 fix: new dev volumes and service files
fix: new parser version (4) that will fix data layout (applications goes to /applications, services goes to /services)
2024-09-30 14:16:37 +02:00
Andras Bacsai
68f541ded3 fix: filebrowser template 2024-09-30 14:16:37 +02:00
Andras Bacsai
afdf4cd5a8 service restart does no cleanup docker 2024-09-30 14:16:37 +02:00
Andras Bacsai
11dbce1471 Merge pull request #3630 from jordanamr/fix/filebrowser-template
fix(templates): filebrowser FQDN env variable
2024-09-30 14:16:14 +02:00
Andras Bacsai
e1037ac031 Merge branch 'next' into fix/filebrowser-template 2024-09-30 14:16:01 +02:00
Andras Bacsai
88c2b1e841 Merge pull request #3609 from peaklabs-dev/fix-2185
Fix: Remove memlock from dragonfly DB as it caused problems for some users
2024-09-30 12:18:38 +02:00
Andras Bacsai
f46fd8872a Merge pull request #3608 from peaklabs-dev/add-debugbar
Feat: Add debug bar for better debugging
2024-09-30 12:15:04 +02:00
Andras Bacsai
b6b4d93658 fix: compose based terminal 2024-09-30 11:58:28 +02:00
Andras Bacsai
8385b7dfe8 fix: handle edge case when build variables and env variables are in different format 2024-09-30 11:15:23 +02:00
Andras Bacsai
a660117015 use latest helper in dev 2024-09-30 11:14:53 +02:00
Andras Bacsai
6fe31c26a3 refactor: remove unnecessary code 2024-09-30 11:14:40 +02:00
Andras Bacsai
cccd05f322 chore: Refactor code to improve SSH key handling and storage 2024-09-29 20:12:30 +02:00
Andras Bacsai
795af8de52 Make sure key is not created with wrong private_key 2024-09-29 20:05:02 +02:00
Jordan Rey
606d46eef0 fix(templates): filebrowser FQDN env variable 2024-09-29 15:36:41 +02:00
Andras Bacsai
56ec0a3004 chore: Update version numbers to 4.0.0-beta.348 2024-09-29 13:27:45 +02:00
Norman
b84576ce87 Merge branch 'main' into main 2024-09-28 18:18:47 +03:00
Andras Bacsai
019a8bfaa6 Merge pull request #3606 from coollabsio/next
v4.0.0-beta.347
2024-09-28 11:14:33 +02:00
Andras Bacsai
fe20480fdc fix: proxy 2024-09-28 11:14:14 +02:00
Andras Bacsai
71e4b0f9d6 Refactor server form blade template 2024-09-28 11:12:03 +02:00
Andras Bacsai
bc46b0371d Add private key storage for server validation 2024-09-28 10:20:32 +02:00
liberocks
f5785b1f17 fix: save settings after assigning value 2024-09-28 11:28:48 +07:00
liberocks
2219219906 fix: edit is_build_server_enabled upon creating application on other application type 2024-09-28 11:25:00 +07:00
liberocks
9ec3233c0c feat: allow specify use_build_server when creating/updating an application 2024-09-28 11:11:43 +07:00
Norman
f2a9a04461 refactor: Update search input placeholder in resource index view 2024-09-28 01:14:54 +00:00
Andras Bacsai
7c269bd0bf chore: custom vite envs 2024-09-27 21:07:56 +02:00
peaklabs-dev
b99474ac73 Fix: Remove memlock as it caused problems for some users 2024-09-27 17:38:34 +02:00
Andras Bacsai
fcadb20d58 chore: Update database startup heading title 2024-09-27 17:32:37 +02:00
Andras Bacsai
24733d0670 Merge pull request #3572 from ejscheepers/main
Fixed NEXT_PUBLIC_API_URI in plunk.yaml
2024-09-27 17:32:18 +02:00
Andras Bacsai
d3898ee320 chore: Update database startup heading title 2024-09-27 17:31:48 +02:00
Andras Bacsai
09cd087cd0 chore: Update backup deletion labels to use language files 2024-09-27 17:29:36 +02:00
Andras Bacsai
d75e55111a chore: Update web.php to cast server port as integer 2024-09-27 17:26:27 +02:00
Andras Bacsai
8e6c7eaeda chore: Update API Tokens view to include link to Settings menu 2024-09-27 17:23:36 +02:00
Andras Bacsai
9489a0881f Merge pull request #3542 from peaklabs-dev/fix-dev-volumes
Fix: Volume issues in development
2024-09-27 17:15:39 +02:00
Andras Bacsai
691f606fa0 Merge branch 'next' into fix-dev-volumes 2024-09-27 17:13:02 +02:00
Andras Bacsai
ac60b8cb22 chore: Remove unused .env.development.example file 2024-09-27 17:11:55 +02:00
Andras Bacsai
46ae2a4ae1 update service-template 2024-09-27 17:08:32 +02:00
Andras Bacsai
f0981078ce chore: Update versions.json file 2024-09-27 17:08:04 +02:00
Andras Bacsai
0816169cb6 Merge pull request #3591 from clho40/next
Next
2024-09-27 17:07:45 +02:00
Andras Bacsai
df8c5c6bf2 Merge branch 'next' into next 2024-09-27 17:06:52 +02:00
Andras Bacsai
ea1be05c17 Merge pull request #3594 from peaklabs-dev/lock-closed-issues-and-prs
Feat: GitHub action that locks closed issues, discussions, and PRs
2024-09-27 17:05:37 +02:00
Andras Bacsai
eafba5d115 fix typo 2024-09-27 17:04:36 +02:00
Andras Bacsai
40eec96a1e Merge pull request #3568 from tiennm99/main
[Twenty] (Fix) typo
2024-09-27 17:04:04 +02:00
Andras Bacsai
46268f915f Merge pull request #3585 from peaklabs-dev/close-stale-issues-prs
Feat: GH Action that close stale issues and PRs
2024-09-27 17:03:34 +02:00
peaklabs-dev
c237cea280 Merge branch 'coollabsio:main' into add-debugbar 2024-09-27 16:59:21 +02:00
peaklabs-dev
26f18e25aa Create .gitignore 2024-09-27 16:54:24 +02:00
Andras Bacsai
8d02fb9254 chore: Refactor API Tokens component to use isApiEnabled flag 2024-09-27 16:48:17 +02:00
Andras Bacsai
c52fe571f5 Merge pull request #3576 from peaklabs-dev/fix-api-enabeled
Fix: Disable API by default and do not allow API key creation when API is disabled
2024-09-27 16:46:21 +02:00
Andras Bacsai
4995675745 Merge pull request #3564 from peaklabs-dev/remove-pr-build-gh-action
Fix: Removed some GH Actions and ordered issue templates
2024-09-27 16:44:00 +02:00
Andras Bacsai
65356e9bae fixes 2024-09-27 16:43:07 +02:00
Andras Bacsai
6726964f18 Merge pull request #3545 from peaklabs-dev/improve-cleanup
Feat: Improve Docker cleanup
2024-09-27 16:41:03 +02:00
Andras Bacsai
9a766aedc1 Merge branch 'next' into improve-cleanup 2024-09-27 16:40:48 +02:00
Andras Bacsai
96b8ddf664 chore: Add autocomplete attribute to input fields 2024-09-27 16:32:03 +02:00
Andras Bacsai
8adf4051fd refactor: Update delete server confirmation modal buttons 2024-09-27 15:45:59 +02:00
Andras Bacsai
dedf2cf87b fix: proxy fixes 2024-09-27 15:36:51 +02:00
Andras Bacsai
72e12aaf5c feat: Add ContainerStatusTypes enum for managing container status 2024-09-27 15:27:09 +02:00
Andras Bacsai
6c78580f1f refactor: Improve start proxy script to handle existing containers gracefully 2024-09-27 15:27:05 +02:00
Andras Bacsai
75c8f6c94a Update version numbers to 4.0.0-beta.347 2024-09-27 14:45:07 +02:00
Andras Bacsai
55847d90c0 Merge pull request #3518 from coollabsio/improve-seeders
Refactor: Improve seeders
2024-09-27 14:43:56 +02:00
Andras Bacsai
b374addc37 Merge pull request #3490 from peaklabs-dev/improve-release.md
Feat: Improve release.md
2024-09-27 14:40:08 +02:00
peaklabs-dev
5541c737b7 Remove all deleted seeders 2024-09-27 12:24:58 +02:00
peaklabs-dev
39015e7d6d Fix: Typo in install script 2024-09-27 12:17:54 +02:00
Andras Bacsai
4c3beb6715 Merge pull request #3586 from coollabsio/next
v4.0.0-beta.346
2024-09-27 09:53:25 +02:00
Andras Bacsai
a807016cab Merge pull request #3574 from LEstradioto/fix-enhance-terminal
Terminal fixes
2024-09-27 09:49:31 +02:00
Andras Bacsai
2c2173e773 refactor: Update environment variable name for uptime-kuma service 2024-09-27 08:48:17 +02:00
peaklabs-dev
2364fac240 Update lock-closed-issues-discussions-and-prs.yml 2024-09-26 19:10:40 +02:00
Ho Chia Leung
68c065baa0 Merge pull request #1 from clho40/clho40-logto-entrypoint-fixes
Clho40 logto entrypoint fixes
2024-09-26 16:43:29 +02:00
Ho Chia Leung
bfc4474815 merged the latest entrypoint configuration from Logto's source
The previous entrypoint configuration throws error error: type "application_type" does not exist
2024-09-26 16:41:38 +02:00
peaklabs-dev
0e31c02f9e Update lock-closed-issues-discussions-and-prs.yml 2024-09-26 14:27:58 +02:00
peaklabs-dev
6ee80c3ea7 Update lock-closed-issues-discussions-and-prs.yml 2024-09-26 14:20:21 +02:00
peaklabs-dev
b9d24913e8 Create lock-closed-issues-discussions-and-prs.yml 2024-09-26 14:10:16 +02:00
Andras Bacsai
9a1c9124ae refactor: Add support for IPv6 addresses in sslip function 2024-09-26 13:47:13 +02:00
Andras Bacsai
306c4dcbc5 chore: Update version numbers to 4.0.0-beta.346 2024-09-26 13:47:07 +02:00
peaklabs-dev
ed7e4360c8 Merge branch 'close-stale-issues-prs' of https://github.com/peaklabs-dev/coolify into close-stale-issues-prs 2024-09-26 13:39:42 +02:00
peaklabs-dev
f02db282ca Update manage-stale-issues-and-prs.yml 2024-09-26 13:39:36 +02:00
Andras Bacsai
3535dbb98f Merge pull request #3583 from coollabsio/next
v4.0.0-beta.345
2024-09-26 13:21:16 +02:00
Andras Bacsai
84b2af53d8 refactor: Fix modal input closeOutside prop in configuration.blade.php 2024-09-26 13:20:06 +02:00
Andras Bacsai
ba70451765 refactor: Update install.sh script to remove redirection of upgrade output to /dev/null 2024-09-26 13:07:06 +02:00
Andras Bacsai
f3ec4ca4a3 refactor: Improve modal confirmation titles and button labels 2024-09-26 13:07:01 +02:00
Andras Bacsai
174923de98 refactor: Update ProductionSeeder to fix issue with coolify_key assignment 2024-09-26 13:06:56 +02:00
Andras Bacsai
68ab8dc14c refactor: Add localhost as Server if it doesn't exist and not in cloud environment 2024-09-26 12:31:39 +02:00
Andras Bacsai
b218356f2d chore: Update version numbers to 4.0.0-beta.345 2024-09-26 12:31:36 +02:00
Andras Bacsai
e83aecebc3 Merge pull request #3559 from coollabsio/next
v4.0.0-beta.344
2024-09-26 12:22:48 +02:00
Andras Bacsai
0bb1f57ea7 fix: deploy key based deployments 2024-09-26 12:19:49 +02:00
peaklabs-dev
49144552b3 Merge branch 'coollabsio:main' into close-stale-issues-prs 2024-09-26 11:47:40 +02:00
peaklabs-dev
111e9ba3a3 Fix: Disable API by default 2024-09-26 10:45:18 +02:00
Andras Bacsai
d006edc485 refactor: Add localhost as Server if it doesn't exist and not in cloud environment 2024-09-26 10:37:02 +02:00
Andras Bacsai
aa28c99827 refactor: Update OS_TYPE for Asahi Linux in install.sh script 2024-09-26 10:36:59 +02:00
peaklabs-dev
47920f1191 Feat: If API is disabeled do not show API token creation stuff 2024-09-26 10:32:12 +02:00
Luan Estradioto
6bd8583eab - resize check to int
- modularize, tree-shake terminal, idempotency on navigations
2024-09-25 22:14:05 -03:00
EJS00102
92dbd44c8c Fixed NEXT_PUBLIC_API_URI in plunk.yaml 2024-09-26 00:01:38 +02:00
tiennm99
be6caf8d76 [Twenty] (Fix) typo 2024-09-25 23:41:04 +07:00
peaklabs-dev
6cf5a2802b ordered the issue templates 2024-09-25 13:28:33 +02:00
peaklabs-dev
9c756ad6c5 Delete docker-image.yml 2024-09-25 13:28:17 +02:00
peaklabs-dev
20de0c56a4 Delete fix-php-code-style-issues 2024-09-25 13:28:13 +02:00
peaklabs-dev
0058f9aa13 Delete pr-build.yml 2024-09-25 13:28:08 +02:00
Andras Bacsai
567bbe9d0b chore: Update version numbers to 4.0.0-beta.344 2024-09-25 10:27:51 +02:00
Andras Bacsai
59d2c9748a fix: make sure to reload window if app status changes 2024-09-25 10:27:23 +02:00
Andras Bacsai
bd2e1ad9fe refactor: Fix typo in execute-container-command.blade.php 2024-09-25 10:25:35 +02:00
Andras Bacsai
43df918e43 Merge pull request #3550 from coollabsio/next
v4.0.0-beta.343
2024-09-25 09:29:54 +02:00
Andras Bacsai
1a8d15390d refactor: Update confirmation label in danger.blade.php template 2024-09-25 09:27:17 +02:00
Andras Bacsai
4af6caa79c refactor: Update checkbox component to support full width option 2024-09-25 09:16:28 +02:00
peaklabs-dev
dfd6fdf0a8 Merge pull request #3554 from coollabsio/next
Next
2024-09-25 00:00:49 +02:00
peaklabs-dev
b0635327a1 Revert "Fix: Typo"
This reverts commit ca21545d7b.
2024-09-25 00:00:26 +02:00
Andras Bacsai
8e91d958be refactor: Improve layout and add checkbox for task enablement in scheduled task form 2024-09-24 21:29:55 +02:00
Andras Bacsai
17d55630d5 refactor: Group and sort fields in StackForm by service name and password status 2024-09-24 21:17:07 +02:00
Andras Bacsai
70dfa101ef refactor: Improve label positioning in input and checkbox components 2024-09-24 21:04:04 +02:00
Andras Bacsai
d6b4e33db3 fix: exited services statuses 2024-09-24 20:40:41 +02:00
Andras Bacsai
a9670bd6eb refactor: Remove commented out code and improve environment variable handling in newParser function 2024-09-24 18:38:35 +02:00
Andras Bacsai
02165c2b9f chore: Update version numbers to 4.0.0-beta.343 2024-09-24 18:22:06 +02:00
Andras Bacsai
afbdd2eb06 fix: parser 2024-09-24 18:21:31 +02:00
Andras Bacsai
b0e6014e8f Merge pull request #3546 from coollabsio/dependabot/npm_and_yarn/rollup-3.29.5
chore(deps): bump rollup from 3.29.4 to 3.29.5
2024-09-24 14:34:17 +02:00
dependabot[bot]
0f13af2eca chore(deps): bump rollup from 3.29.4 to 3.29.5
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 12:27:43 +00:00
Andras Bacsai
d4d9268f12 Merge pull request #3506 from coollabsio/next
v4.0.0-beta.342
2024-09-24 14:26:04 +02:00
peaklabs-dev
f9dbc30812 Feat: Use the new confirmation flow 2024-09-24 13:06:03 +02:00
Andras Bacsai
62459f9a95 refactor: Remove unused code in DatabaseBackupStatusJob and PopulateSshKeysDirectorySeeder 2024-09-24 11:57:13 +02:00
Andras Bacsai
eb80a7e2f7 chore: Update Docker commands to start proxy 2024-09-24 10:53:12 +02:00
Andras Bacsai
57d8930f9e chore: Update cleanup command to use Redis instead of queue 2024-09-23 23:48:12 +02:00
Andras Bacsai
688c27c901 fix: cloudflare tunnel configuration, ui, etc 2024-09-23 23:18:23 +02:00
Andras Bacsai
480ae3de8a Refactor Server.php to improve default 404 redirect setup 2024-09-23 22:57:24 +02:00
Andras Bacsai
21a5b4ce80 chore: Refactor pre-commit hook to improve performance and readability 2024-09-23 22:16:28 +02:00
Andras Bacsai
12a0d71e47 Merge pull request #3543 from peaklabs-dev/cf-production-ready
Feat: Make cloudflare production ready
2024-09-23 21:53:23 +02:00
peaklabs-dev
5ed7ae3d3e remove ray 2024-09-23 21:45:59 +02:00
Andras Bacsai
6dd3adba4d refactor: Simplify SSH key synchronization logic 2024-09-23 21:16:25 +02:00
Andras Bacsai
08d58eb8f8 chore: Update CONTRIBUTING.md with troubleshooting note about database migrations 2024-09-23 20:55:20 +02:00
Andras Bacsai
46a7937761 test 2024-09-23 20:54:56 +02:00
Andras Bacsai
13a5536cff test 2024-09-23 20:54:18 +02:00
Andras Bacsai
2381706465 chore: Refactor pre-commit hook to improve performance and readability 2024-09-23 20:52:13 +02:00
Andras Bacsai
7a0f054f1a test 2024-09-23 20:49:26 +02:00
Andras Bacsai
00e1464185 chore: Add modified files to the commit 2024-09-23 20:48:57 +02:00
Andras Bacsai
fac4a8aaf9 hooks? 2024-09-23 20:45:42 +02:00
Andras Bacsai
2841675691 refactor 2024-09-23 20:31:50 +02:00
Andras Bacsai
573e5c4913 refactor 2024-09-23 20:29:22 +02:00
peaklabs-dev
6934a746f7 Fix: Make helper text more clean to use a FQDN and not an URL 2024-09-23 19:57:42 +02:00
Andras Bacsai
b570ccd7d3 format 2024-09-23 19:51:31 +02:00
Andras Bacsai
68efd4b553 fix: migrations 2024-09-23 19:50:26 +02:00
Andras Bacsai
e00ec2f75b chore: Remove unused migration file for populating SSH keys and clearing mux directory 2024-09-23 19:46:45 +02:00
peaklabs-dev
1bb192f3e2 Fix: Cloudflare tunnel 2024-09-23 19:23:46 +02:00
peaklabs-dev
e039f183da Merge pull request #16 from coollabsio/next
Next
2024-09-23 18:50:19 +02:00
peaklabs-dev
7aab44645f Merge branch 'cf-production-ready' into next 2024-09-23 18:50:08 +02:00
peaklabs-dev
7c52d7ef9e Update CONTRIBUTING.md 2024-09-23 17:33:06 +02:00
peaklabs-dev
83c6dcb063 Fix: Persist DBs, services and so on stored in data/coolify 2024-09-23 17:31:20 +02:00
peaklabs-dev
6bb21da9f3 Update CONTRIBUTING.md 2024-09-23 17:20:31 +02:00
peaklabs-dev
5a46ef42c8 Feat: clean new volume name for dev volumes 2024-09-23 17:03:10 +02:00
Andras Bacsai
04e504bb8b chore: Refactor DockerCleanupJob to remove unused middleware and uniqueId method 2024-09-23 12:10:46 +02:00
Andras Bacsai
960f970822 chore: Remove unused middleware and uniqueId method in DockerCleanupJob 2024-09-23 12:03:14 +02:00
Andras Bacsai
ae62781c6a fix: cloudflared service 2024-09-23 11:36:57 +02:00
Andras Bacsai
b42c15b2e5 Merge pull request #3483 from djsisson/update-CF-Tunnel-template
Update cloudflared.yaml
2024-09-23 11:36:52 +02:00
Andras Bacsai
c77cc45e0d fix: Logto service 2024-09-23 11:33:53 +02:00
Andras Bacsai
310708f1b6 Merge pull request #3480 from JustMrMendez/patch-1
FIXES: #3322 deploy DB alterations before starting the service
2024-09-23 11:33:55 +02:00
Andras Bacsai
8386aaf95b Merge pull request #3481 from peaklabs-dev/new-pull-request-template
Feat: New pull request template
2024-09-23 11:32:24 +02:00
Andras Bacsai
6d8d91802b chore: Update install.sh version to 1.6 2024-09-23 11:30:57 +02:00
Andras Bacsai
c0a1f15d34 Merge pull request #3472 from disjukr/asahi-linux
Add support to install.sh for Asahi Linux
2024-09-23 11:29:26 +02:00
Andras Bacsai
0222aa137d feat(api): add endpoint to execute a command 2024-09-23 11:09:14 +02:00
Andras Bacsai
4d36bc4742 Merge pull request #3535 from Luca-Sordetti/main
feat(api): add an endpoint to execute a command
2024-09-23 10:53:28 +02:00
Andras Bacsai
6639379ba6 Merge pull request #3522 from InfiniBrains/main
Add hint to "build variable" check in the enviroment variable tab. Remember the user what should be doing in order to make it work.
2024-09-23 10:49:59 +02:00
Andras Bacsai
d60ff107c4 Merge pull request #3539 from peaklabs-dev/fix-dev-error
Fix: Ray error on development
2024-09-23 10:49:39 +02:00
Andras Bacsai
a468ce77f0 refactor: Update shared.php to include predefined ports for services 2024-09-23 10:46:34 +02:00
Andras Bacsai
1fb0ee0db5 refactor: Add Postiz service to compose file (disabled for now) 2024-09-23 10:46:30 +02:00
Andras Bacsai
9281dda03f refactor: Fix typo in subscription URLs 2024-09-23 10:46:19 +02:00
peaklabs-dev
d3485cea8d Update pull_request_template.md 2024-09-23 10:27:48 +02:00
peaklabs-dev
92772f9132 Merge branch 'next' into fix-dev-error 2024-09-23 10:20:26 +02:00
Andras Bacsai
6f8c4a4ce4 refactor: Update select.blade.php to improve trademarks policy display 2024-09-23 09:44:33 +02:00
Andras Bacsai
85ab772acd refactor: Update select.blade.php to improve trademarks policy display 2024-09-23 09:43:17 +02:00
Andras Bacsai
6336125ca2 Merge pull request #3516 from nathanialhenniges/main
Added link for more services to the coolfiy docs
2024-09-23 09:42:49 +02:00
Andras Bacsai
341c7e3598 refactor: Update environment variables for services in compose files 2024-09-23 09:26:53 +02:00
Andras Bacsai
ff9b68b450 refactor: Remove unnecessary code in shared.php file 2024-09-23 09:26:49 +02:00
Andras Bacsai
a072a00926 Merge pull request #3525 from gabrielcossette/fix_ghost_template_domain
Fix Ghost Template domain generation
2024-09-23 09:05:34 +02:00
Andras Bacsai
b84d39ba56 refactor: Update confirmation button text for deletion actions 2024-09-23 08:58:04 +02:00
peaklabs-dev
164a213ab1 Fix: force helper image removal 2024-09-22 20:36:41 +02:00
peaklabs-dev
696acb71fe Improve helper text for all cleanup settings 2024-09-22 20:26:45 +02:00
peaklabs-dev
ff5e445b43 Feat: Manual cleanup button and unused volumes and network deletion 2024-09-22 20:02:51 +02:00
Luca-Sordetti
4e167dc539 feat(api): add an endpoint to execute a command 2024-09-22 12:38:25 +02:00
peaklabs-dev
76655a7c7d Remove dev .env form nightly as there is also no dev compose in nightly 2024-09-21 01:31:17 +02:00
peaklabs-dev
a26c1f0052 Update CONTRIBUTING.md 2024-09-21 01:11:03 +02:00
peaklabs-dev
cb4f7755c3 Update CONTRIBUTING.md 2024-09-21 01:08:28 +02:00
peaklabs-dev
b7139f8af8 remove clockwork 2024-09-21 00:20:16 +02:00
peaklabs-dev
a5b3fef1d8 Fix: ray error because port is not uncommented 2024-09-21 00:20:01 +02:00
peaklabs-dev
fca64d6726 Fix: volumes on development environment 2024-09-20 23:12:08 +02:00
Gabriel Cossette
b9a026b488 Fix Ghost Template domain generation
Signed-off-by: Gabriel Cossette <gabriel.cossette@mapgears.com>
2024-09-20 14:29:11 -06:00
Alexandre Tolstenko Nogueira
a4d0f1da9e Add hint to "build variable" check in the enviroment variable tab. remember the user what should be doing in order to make it work. #3477 2024-09-20 14:12:15 -04:00
Andras Bacsai
eb9bbf3eda refactor: Improve attribute sanitization in Server model 2024-09-20 18:14:52 +02:00
Andras Bacsai
be42f15711 refactor: Update ServerSeeder and PopulateSshKeysDirectorySeeder 2024-09-20 17:28:55 +02:00
peaklabs-dev
768b39dd25 Revert "remove team seeders"
This reverts commit 000a348c90.
2024-09-20 15:45:18 +02:00
peaklabs-dev
000a348c90 remove team seeders 2024-09-20 15:42:24 +02:00
peaklabs-dev
56f7eb769d Remove commented out seeders 2024-09-20 15:42:06 +02:00
peaklabs-dev
cc77775f20 Remove not needed seeders to make management simpler 2024-09-20 15:32:56 +02:00
peaklabs-dev
ca21545d7b Fix: Typo 2024-09-20 15:01:34 +02:00
Andras Bacsai
fa375e2fd8 refactor: Update install.sh script to check if coolify-db volume exists before generating SSH key 2024-09-20 14:39:58 +02:00
Andras Bacsai
760cf8aeb5 refactor: Update PrivateKey model to use ownedByCurrentTeam() scope for cleanupUnusedKeys() 2024-09-20 13:05:51 +02:00
Andras Bacsai
f9238ce263 Merge pull request #3509 from coollabsio/delete-unused-ssh-keys
Feat: Delete unused ssh keys button
2024-09-20 13:00:54 +02:00
Andras Bacsai
c0898f0568 refactor: Remove unnecessary code in PrivateKey model 2024-09-20 12:51:02 +02:00
Andras Bacsai
5b00b66f24 refactor: Update PrivateKey model to improve code readability and maintainability 2024-09-20 12:27:55 +02:00
Andras Bacsai
b81f9114d9 chore: Update ProductionSeeder to call OauthSettingSeeder and PopulateSshKeysDirectorySeeder 2024-09-20 11:34:13 +02:00
Andras Bacsai
be8573c828 chore: Update SSH key generation in install.sh script 2024-09-20 11:04:26 +02:00
Andras Bacsai
f1881d5c35 refactor: Update CleanupHelperContainersJob to use more efficient Docker command 2024-09-20 10:08:37 +02:00
Nathanial Henniges
699e76637b Added link for more services to the coolfiy docs 2024-09-20 03:02:31 -05:00
peaklabs-dev
dbc723089b Feat: Delete unused private keys button 2024-09-19 19:27:25 +02:00
Andras Bacsai
fc6f5d82db chore: Add SSH key for localhost in ProductionSeeder 2024-09-19 17:26:59 +02:00
Andras Bacsai
1815c9dccf fix: store original root key in the original location 2024-09-19 16:35:10 +02:00
Andras Bacsai
7d54fe9c18 chore: Update remove-labels-and-assignees-on-close.yml 2024-09-19 13:26:27 +02:00
Andras Bacsai
e9fbb7d2b0 fix: coolify-db should not be in the managed resources 2024-09-19 13:23:48 +02:00
Andras Bacsai
2d94ebf7f1 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-09-19 13:16:10 +02:00
Andras Bacsai
c874261c5b feat: Add nullable constraint to 'fingerprint' column in private_keys table 2024-09-19 13:16:08 +02:00
Andras Bacsai
1c5fd8d668 refactor: Update getAJoke function to use HTTPS for API request 2024-09-19 13:16:05 +02:00
Andras Bacsai
874e04e844 Merge pull request #3505 from peaklabs-dev/main
Fix: Update remove-labels-and-assignees-on-close.yml for PRs
2024-09-19 13:01:40 +02:00
peaklabs-dev
c46715db3c Merge branch 'main' of https://github.com/peaklabs-dev/coolify 2024-09-19 13:01:19 +02:00
peaklabs-dev
aa0fe922ea Update remove-labels-and-assignees-on-close.yml 2024-09-19 13:01:07 +02:00
peaklabs-dev
60e9fcc202 Update remove-labels-and-assignees-on-close.yml 2024-09-19 12:55:20 +02:00
Andras Bacsai
91485b5d4d refactor: Update getAJoke function to exclude offensive jokes 2024-09-19 12:50:51 +02:00
Andras Bacsai
6ec1b9b3f5 disable remove labels for issues 2024-09-19 12:47:35 +02:00
Andras Bacsai
240352f4b2 Merge pull request #3504 from coollabsio/fix-ssh-keys
Fix ssh keys
2024-09-19 12:44:56 +02:00
Andras Bacsai
cfd9ae9817 Merge pull request #3454 from peaklabs-dev/fix-ssh-keys
Fix: SSH Keys, Multiplexing issues and a lot of other small things for dev and prod
2024-09-19 12:44:23 +02:00
Andras Bacsai
f65789bdbb fix: proxy status 2024-09-19 12:32:56 +02:00
Andras Bacsai
9518040d23 refactor: Remove CleanupSshKeysJob from schedule in Kernel.php 2024-09-19 12:06:56 +02:00
Andras Bacsai
631b4e6438 Merge branch 'next' into fix-ssh-keys 2024-09-19 11:45:12 +02:00
Andras Bacsai
d47bd047bf fixes 2024-09-19 11:24:21 +02:00
Andras Bacsai
a65b62332b refactor: Remove commented out code in Navbar.php 2024-09-19 10:57:34 +02:00
Andras Bacsai
eb10bbb888 Merge pull request #3502 from coollabsio/fix-#2546-deletion-issues
Fixes
2024-09-19 10:41:57 +02:00
Andras Bacsai
4b02debf97 Merge pull request #3024 from peaklabs-dev/fix-#2546-deletion-issues
Feat: Fix all deletion issues, add graceful shutdown to everything, new delete confirmation process
2024-09-19 10:40:27 +02:00
Andras Bacsai
430fdcf2e9 chore: Update version numbers to 4.0.0-beta.342 2024-09-19 10:35:54 +02:00
Andras Bacsai
636de24b6c Merge pull request #3501 from coollabsio/#2546
refactor: Update Docker cleanup label in Heading.php and Navbar.php
2024-09-19 10:32:09 +02:00
Andras Bacsai
2e3267ee94 Merge branch 'fix-#2546-deletion-issues' into #2546 2024-09-19 10:32:02 +02:00
Andras Bacsai
98e744e808 refactor: Update Docker cleanup label in Heading.php and Navbar.php 2024-09-19 10:27:44 +02:00
Andras Bacsai
532f5e351e fixes 2024-09-18 21:24:42 +02:00
Andras Bacsai
182087cf1b a few changes here and there 2024-09-18 21:18:47 +02:00
peaklabs-dev
6676c6bd64 Merge branch 'coollabsio:main' into new-pull-request-template 2024-09-18 20:44:42 +02:00
peaklabs-dev
22326ec0b4 Merge branch 'coollabsio:main' into improve-release.md 2024-09-18 20:44:24 +02:00
peaklabs-dev
8153e2f63b Merge branch 'coollabsio:main' into fix-ssh-keys 2024-09-18 20:43:59 +02:00
peaklabs-dev
b0793c879a Merge branch 'coollabsio:main' into close-stale-issues-prs 2024-09-18 20:43:01 +02:00
peaklabs-dev
dce8555aa7 Merge branch 'coollabsio:main' into add-debugbar 2024-09-18 20:42:29 +02:00
peaklabs-dev
35857de697 Merge branch 'coollabsio:main' into cf-production-ready 2024-09-18 20:42:18 +02:00
peaklabs-dev
cb177dae58 Update RELEASE.md 2024-09-18 18:36:04 +02:00
peaklabs-dev
4e8708051d Update RELEASE.md 2024-09-18 18:34:54 +02:00
peaklabs-dev
5b27eef984 Update RELEASE.md 2024-09-18 18:28:21 +02:00
peaklabs-dev
d1f28b42e5 Update RELEASE.md 2024-09-18 18:21:08 +02:00
peaklabs-dev
513ca6f57d Update RELEASE.md 2024-09-18 18:09:03 +02:00
Andras Bacsai
5ec45d547a Merge branch 'next' into fix-#2546-deletion-issues 2024-09-18 18:05:06 +02:00
peaklabs-dev
4aed40fb65 Update RELEASE.md 2024-09-18 17:15:39 +02:00
Darren Sisson
d2cf53578a Update cloudflared.yaml
add network mode host to template, otherwise cloudflared has no access to localhost
2024-09-18 16:09:24 +01:00
Mr. Mendez
1fa42ec82d FIXES: #3322 deploy DB alterations before updating
FIXES: #3322 

[Bug]: logto stop working after pulling latest image #3322
2024-09-18 08:59:02 -04:00
Andras Bacsai
e42c7e258c Merge pull request #3479 from coollabsio/next
v4.0.0-beta.341
2024-09-18 14:45:00 +02:00
Andras Bacsai
2c210abf57 Merge pull request #3478 from dennisblume/caddy-terminal-fix
Fix WebSocket connection for Terminal page when using Caddy
2024-09-18 14:36:48 +02:00
Dennis Blume
98ba7ac28c Fix WebSocket connection for Terminal page when using Caddy 2024-09-18 13:33:38 +02:00
Andras Bacsai
5772f2c3d9 feat: Add buddy logo 2024-09-18 13:18:31 +02:00
Andras Bacsai
cb9c569d4b Merge pull request #3470 from Skeyelab/add-buddy-svg
featu update: adding budge logo
2024-09-18 13:18:10 +02:00
Andras Bacsai
5616e588e9 Merge pull request #3473 from coollabsio/dependabot/npm_and_yarn/vite-4.5.5
chore(deps-dev): bump vite from 4.5.3 to 4.5.5
2024-09-18 12:56:34 +02:00
Andras Bacsai
40e844fab4 refactor: Remove unnecessary console.log statements in terminal.blade.php 2024-09-18 12:54:31 +02:00
Andras Bacsai
2e56edd939 refactor: Update WebSocket connection initialization in terminal.blade.php 2024-09-18 09:51:20 +02:00
Andras Bacsai
8179a5c6a3 feat: custom terminal host 2024-09-18 09:43:47 +02:00
Andras Bacsai
d0518153fb fix: generated fqdn for SERVICE_FQDN_APP_3000 magic envs 2024-09-18 09:21:57 +02:00
dependabot[bot]
29236bf101 chore(deps-dev): bump vite from 4.5.3 to 4.5.5
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.3 to 4.5.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 07:13:17 +00:00
Andras Bacsai
2d306d56ab chore: Update version numbers to 4.0.0-beta.341 2024-09-18 09:13:04 +02:00
JongChan Choi
e5768999e4 chore: Update install.sh to support Asahi Linux 2024-09-18 13:22:07 +09:00
Eric Dahl
b076db2eed update 2024-09-17 18:45:41 -04:00
Eric Dahl
3534424dc8 adding budge logo 2024-09-17 18:41:26 -04:00
peaklabs-dev
d6f498e6bb Update RELEASE.md 2024-09-17 23:05:03 +02:00
peaklabs-dev
3e0a5191b9 Update RELEASE.md 2024-09-17 23:01:56 +02:00
peaklabs-dev
28cde9fd50 Update RELEASE.md 2024-09-17 22:52:47 +02:00
peaklabs-dev
e415a99f71 Update RELEASE.md 2024-09-17 22:28:00 +02:00
peaklabs-dev
3b6c3609c3 Update pull_request_template.md 2024-09-17 22:11:30 +02:00
peaklabs-dev
95a97cf9cd Update pull_request_template.md 2024-09-17 22:09:00 +02:00
peaklabs-dev
7b33ef705b Update pull_request_template.md 2024-09-17 22:07:19 +02:00
peaklabs-dev
39449584ac Update pull_request_template.md 2024-09-17 22:04:19 +02:00
peaklabs-dev
19458e7445 Update pull_request_template.md 2024-09-17 21:47:19 +02:00
Andras Bacsai
2d9c728a64 Merge pull request #3468 from coollabsio/next
v4.0.0-beta.340
2024-09-17 17:31:39 +02:00
Andras Bacsai
12a8e9b0e1 fix: only update helper image in DB 2024-09-17 17:29:42 +02:00
Andras Bacsai
649cc2dac2 chore: Update version numbers to 4.0.0-beta.340 2024-09-17 17:25:38 +02:00
Andras Bacsai
c169f1f64b Merge pull request #3467 from coollabsio/next
v4.0.0-beta.339
2024-09-17 17:20:27 +02:00
Andras Bacsai
5ecf31d1fc refactor: Remove unnecessary code in Terminal.blade.php 2024-09-17 17:20:03 +02:00
Andras Bacsai
e937d30545 fix: move terminal to separate view on services 2024-09-17 17:15:34 +02:00
peaklabs-dev
bf48b33e64 Update pull_request_template.md 2024-09-17 17:14:15 +02:00
Andras Bacsai
595a2414b1 fix: if you exit a container manually, it should close the underlying tty as well 2024-09-17 16:48:58 +02:00
Andras Bacsai
07ed726c88 refactor: Remove unnecessary code in Terminal.php 2024-09-17 16:48:30 +02:00
Andras Bacsai
d373815f98 refactor: Add authorization check in ExecuteContainerCommand mount method 2024-09-17 16:28:28 +02:00
peaklabs-dev
d9181bd00b Fix: Multiplexing do not write file manually 2024-09-17 16:22:53 +02:00
peaklabs-dev
d13e2c0865 Fix: Clear mux directory 2024-09-17 15:57:57 +02:00
peaklabs-dev
42ff7b19a4 Fix: Few multiplexing things 2024-09-17 15:54:22 +02:00
peaklabs-dev
283fcc87a5 Fix: SSH algorhytm text 2024-09-17 15:31:44 +02:00
peaklabs-dev
ea3501ada6 Fix: SSH Multiplexing for Jobs 2024-09-17 15:31:05 +02:00
peaklabs-dev
175f4b9ae1 use shared functions when possible 2024-09-17 14:47:02 +02:00
peaklabs-dev
2bc74c75e1 Remove duplicated code 2024-09-17 14:43:02 +02:00
peaklabs-dev
4ac2758d70 Update .env.development.example 2024-09-17 14:35:08 +02:00
peaklabs-dev
bdc0fc87f0 Fix: UI bug, do not write ssh key to disk in server dialog 2024-09-17 14:33:24 +02:00
peaklabs-dev
845d32c94c Fix: Do not delete SSH Key from disk during server validation error 2024-09-17 14:33:04 +02:00
peaklabs-dev
6a6b947fba Fix: Make sure in use private keys are not deleted 2024-09-17 14:32:44 +02:00
peaklabs-dev
2ec66fd146 FIx: Server ID 0 2024-09-17 14:08:34 +02:00
peaklabs-dev
ccbbfd8908 Fix: ID issues on dev seeders 2024-09-17 14:03:31 +02:00
peaklabs-dev
43895419ff Remove unused code 2024-09-17 13:45:05 +02:00
peaklabs-dev
1c78067386 Feat: Add ssh key fingerprint and generate one for existing keys 2024-09-17 13:20:48 +02:00
peaklabs-dev
871d09bd96 Feat: Move more functions to the PrivateKey Model 2024-09-17 13:20:27 +02:00
peaklabs-dev
2d8bda4fa6 Fix: Private key with ID 2 on dev 2024-09-17 13:06:50 +02:00
peaklabs-dev
95070ab48d Feat: SSH Key cleanup job 2024-09-17 12:57:06 +02:00
Andras Bacsai
8bb8a7faa3 Merge pull request #3464 from peaklabs-dev/remove-labels-assinges-on-close
Feat: GitHub action that removes labels and assignees on close
2024-09-17 12:54:36 +02:00
Andras Bacsai
428c40aab5 chore: Update version numbers to 4.0.0-beta.339 2024-09-17 12:54:23 +02:00
peaklabs-dev
52c4994d44 Feat: remove unused code form multiplexing 2024-09-17 12:44:59 +02:00
Andras Bacsai
6f578855a0 Merge pull request #3463 from coollabsio/next
v4.0.0-beta.388
2024-09-17 12:38:50 +02:00
Andras Bacsai
8967315c49 refactor: terminal / run command 2024-09-17 12:29:36 +02:00
Andras Bacsai
162fb7bfc5 fix: refactor run-command 2024-09-17 12:27:20 +02:00
peaklabs-dev
144508218e Fix: SSH multiplexing 2024-09-17 12:26:11 +02:00
Andras Bacsai
4bdb5c9030 chore: nightly - Update soketi image to version 1.0.1 and versions.json to reflect latest version of realtime container 2024-09-17 12:08:09 +02:00
Andras Bacsai
a2ea8814cf chore: Update soketi image to version 1.0.1 2024-09-17 12:00:57 +02:00
Andras Bacsai
cbc2f1f015 chore: Update versions.json to reflect latest version of realtime container 2024-09-17 11:58:28 +02:00
Andras Bacsai
35b9b7fdf2 fix/feat: able to open terminal to any containers 2024-09-17 11:54:25 +02:00
Andras Bacsai
d92819ab60 fix: Handle WebSocket connection close in terminal.blade.php 2024-09-17 11:30:46 +02:00
Andras Bacsai
ea877f3623 Update version numbers to 4.0.0-beta.338 2024-09-17 11:30:37 +02:00
Andras Bacsai
5818c9cf6b chore: Add validation to prevent selecting 'default' server or container in RunCommand.php 2024-09-17 11:30:29 +02:00
peaklabs-dev
f9375f91ec Feat: Create a Multiplexing Helper 2024-09-16 22:33:43 +02:00
peaklabs-dev
86722939cd Fix. Remove write to SSH key on every remote command execution 2024-09-16 21:34:27 +02:00
peaklabs-dev
70b757df5b remove old function 2024-09-16 19:53:45 +02:00
peaklabs-dev
451272bf11 Fix: Use new function names and logic everywhere 2024-09-16 19:52:55 +02:00
peaklabs-dev
a68fbefadb Fix: Populate SSH keys in dev 2024-09-16 19:34:46 +02:00
peaklabs-dev
b79b4015d7 Feat: Populate SSH key folder 2024-09-16 19:08:15 +02:00
peaklabs-dev
0bfdc1c531 Feat: Store all keys on disk by default 2024-09-16 18:45:08 +02:00
peaklabs-dev
b09017ea46 Feat: new ssh key file name on disk 2024-09-16 18:11:37 +02:00
peaklabs-dev
95fcf38d45 Feat: Add is_sftp and is_server_ssh_key coloums 2024-09-16 18:11:16 +02:00
peaklabs-dev
54c03fae41 Remove ssh key fingerprint as we can just us uuid 2024-09-16 18:10:46 +02:00
Andras Bacsai
b2bab451d3 Merge pull request #3457 from coollabsio/next
v4.0.0-beta.337
2024-09-16 17:29:14 +02:00
Andras Bacsai
7b4559c5e6 fix: install script 2024-09-16 17:28:31 +02:00
peaklabs-dev
ba636a95dc Refactor SSH Keys 2024-09-16 17:24:42 +02:00
Andras Bacsai
682b45a2b5 refactor: Improve Docker network connection command in StartService.php 2024-09-16 16:39:16 +02:00
Andras Bacsai
d44e3a1091 chore: Update docker network connection command in ApplicationDeploymentJob.php 2024-09-16 16:38:34 +02:00
Andras Bacsai
d2d56f136b chore: Update Coolify installer and scripts to include a function for fetching programming jokes 2024-09-16 16:35:52 +02:00
Andras Bacsai
9b48a99798 fix: generate https for minio 2024-09-16 16:35:47 +02:00
Andras Bacsai
1322dc9c23 refactor: Remove unnecessary code in ExecuteContainerCommand.php 2024-09-16 15:43:24 +02:00
Andras Bacsai
f71fb7266d fix: terminal 2024-09-16 15:35:44 +02:00
Andras Bacsai
35f23cfb96 chore: Update version numbers to 4.0.0-beta.337 2024-09-16 15:35:38 +02:00
Andras Bacsai
0c4ce55a15 Merge pull request #3456 from coollabsio/next
chore: Fix syntax error in versions.json
2024-09-16 14:44:08 +02:00
Andras Bacsai
77ee80b562 chore: Fix syntax error in versions.json 2024-09-16 14:43:51 +02:00
Andras Bacsai
2621292dc1 Merge pull request #3425 from coollabsio/next
v4.0.0-beta.336
2024-09-16 14:41:39 +02:00
Andras Bacsai
62a4d7055a fix: update Coolify installer 2024-09-16 14:37:19 +02:00
Andras Bacsai
3fd41c0a92 chore: Update helper version to 1.0.1 2024-09-16 14:30:47 +02:00
Andras Bacsai
0e8291cd86 chore: Update coolify nightly version to 4.0.0-beta.335 2024-09-16 14:30:41 +02:00
Andras Bacsai
175b89ced2 revert: databasebackup 2024-09-16 14:15:06 +02:00
peaklabs-dev
3aee8e030e Fix: Encrypt private SSH keys in the DB 2024-09-16 13:17:39 +02:00
peaklabs-dev
02017334e5 Fix: Make sure invalid private keys can not be added 2024-09-16 13:02:48 +02:00
peaklabs-dev
f9b7841572 Feat: Add a fingerprint to every private key on save, create... 2024-09-16 12:54:48 +02:00
peaklabs-dev
7d39a5089c Feat: Add SSH Key fingerprint to DB 2024-09-16 11:53:26 +02:00
Andras Bacsai
2313fed546 fix: add build.sh to debug logs 2024-09-16 11:50:03 +02:00
Andras Bacsai
e1a6c3e776 chore: Refactor terminal component and select form layout 2024-09-16 11:25:20 +02:00
peaklabs-dev
b41a19d94d Update manage-stale-issues-and-prs.yml 2024-09-16 11:20:46 +02:00
peaklabs-dev
7037c779e2 Update remove-labels-and-assignees-on-close.yml 2024-09-16 11:13:56 +02:00
Andras Bacsai
f124a1e60d chore: Update terminal button text and layout in application heading view 2024-09-16 10:56:11 +02:00
peaklabs-dev
4450ee382f Update manage-stale-issues-and-prs.yml 2024-09-16 10:43:31 +02:00
Andras Bacsai
7ebb0a4579 Merge pull request #3444 from Vahor/trigger-helper-update-in-manual-upgrade
Trigger pull helper image job when upgrading coolify
2024-09-16 10:37:40 +02:00
Andras Bacsai
4962c606bc Merge pull request #3452 from peaklabs-dev/new-issue-onboarding
Feat: New issue onboarding
2024-09-16 10:35:39 +02:00
Andras Bacsai
b728d69ab0 Merge pull request #3451 from peaklabs-dev/add-bounty-issues
Feat: Add Bounty issue type
2024-09-16 10:35:18 +02:00
Andras Bacsai
3d6e53602c Merge pull request #3443 from danielqba/main
Fix tooltip
2024-09-16 10:33:42 +02:00
peaklabs-dev
1c6450da24 Feat: Make sure this action is also triggered on PR issue close 2024-09-16 10:23:05 +02:00
Andras Bacsai
15fe5bd864 chore: Update branch restriction for push event in coolify-helper.yml 2024-09-16 10:22:02 +02:00
Andras Bacsai
08e4afbbc4 fix: keep-alive ws connections 2024-09-16 10:20:57 +02:00
peaklabs-dev
06fd7286da Update ENHANCEMENT_BOUNTY.yml 2024-09-16 09:45:49 +02:00
peaklabs-dev
fce1e34fc8 Update BUG_REPORT.yml 2024-09-16 09:34:31 +02:00
Vahor
0739e0f5e7 trigger pull helper image job when upgrading coolify 2024-09-15 14:23:57 +02:00
Luis Daniel
faf7ba50e6 Fix tootip
Fix the tooltip from the Server Docker cleanup frequency. Previous value was every 10 minutes. New value is every night at midnight according to changes https://github.com/coollabsio/coolify/releases/tag/v4.0.0-beta.332
2024-09-15 07:19:23 -04:00
peaklabs-dev
e8179ec519 Update ENHANCEMENT_BOUNTY.yml 2024-09-13 20:53:31 +02:00
peaklabs-dev
220f77e737 Create manage-stale-issues-and-prs.yml 2024-09-13 19:14:15 +02:00
peaklabs-dev
7948a0309f Feat: remove labels and assignees on issue close 2024-09-13 18:42:38 +02:00
peaklabs-dev
47277a68ec Create remove-labels-assignees-on-close.yml 2024-09-13 18:24:37 +02:00
peaklabs-dev
60f75f9deb Update config.yml 2024-09-13 18:06:16 +02:00
peaklabs-dev
e90d1af884 Update ENHANCEMENT_BOUNTY.yml 2024-09-13 17:49:30 +02:00
peaklabs-dev
e3046698a5 Create ENHANCEMENT_BOUNTY.yml 2024-09-13 17:42:19 +02:00
peaklabs-dev
bb773e1118 Update BUG_REPORT.yml 2024-09-13 17:01:31 +02:00
Andras Bacsai
dcf91cc034 Update WebSocket URL in terminal.blade.php to include /ws for consistency with the server configuration. 2024-09-13 16:58:16 +02:00
peaklabs-dev
cddf8476de Update BUG_REPORT.yml 2024-09-13 15:31:14 +02:00
Andras Bacsai
51c43e7457 chore: Rename Command Center to Terminal in code and views 2024-09-13 15:18:00 +02:00
Andras Bacsai
7cac243589 chore: Update Dockerfile and workflow for Coolify Realtime (v4) 2024-09-13 14:05:37 +02:00
Andras Bacsai
0cfd8ed5f0 chore: Update Dockerfile and workflow for Coolify Realtime (v4) 2024-09-13 13:46:10 +02:00
Andras Bacsai
8318598cb5 chore: Update Dockerfile and workflow for Coolify Realtime (v4) 2024-09-13 13:17:19 +02:00
Andras Bacsai
2f692da1c9 chore: Update WebSocket URL in terminal.blade.php 2024-09-13 13:16:14 +02:00
Andras Bacsai
ecd98bfcd5 chore: Update APP_NAME environment variable in docker-compose.prod.yml 2024-09-13 13:13:23 +02:00
Andras Bacsai
ba860398f3 chore: Update .env file and docker-compose configuration 2024-09-13 12:47:46 +02:00
Andras Bacsai
1f9af39fa7 chore: Remove unused entrypoint script and update volume mapping 2024-09-13 12:45:59 +02:00
Andras Bacsai
62b995d26c chore: Update Dockerfile and workflow for Coolify Realtime (v4) 2024-09-13 12:40:17 +02:00
Andras Bacsai
b28d118609 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-09-13 12:37:34 +02:00
Andras Bacsai
81d0589c55 Merge pull request #3427 from peaklabs-dev/new-issue-template
Feat: New issue template
2024-09-13 12:37:35 +02:00
Andras Bacsai
07893b432b chore: Update button text for container connection form 2024-09-13 12:37:32 +02:00
Andras Bacsai
568d47d1dd Merge pull request #3417 from lassejlv/patch-1
Fix spell error
2024-09-13 12:35:07 +02:00
Andras Bacsai
85cce4e453 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-09-13 12:22:01 +02:00
Andras Bacsai
888c1f7697 update files 2024-09-13 12:21:02 +02:00
Andras Bacsai
79c8ce7572 Merge pull request #3429 from coollabsio/feat--terminal-pty
fixes for the new terminal
2024-09-13 12:20:53 +02:00
Andras Bacsai
121afaa18c Merge pull request #2586 from LEstradioto/feat--terminal-pty
[FEAT] fully functioning terminal
2024-09-13 12:20:10 +02:00
Andras Bacsai
e48ad87cdb chore: Update terminal styling for better readability 2024-09-13 12:19:13 +02:00
peaklabs-dev
a587cae251 Update BUG_REPORT.yml 2024-09-13 11:44:07 +02:00
peaklabs-dev
c04c1530f5 Update BUG_REPORT.yml 2024-09-13 11:43:24 +02:00
peaklabs-dev
f4ee61fc6a Update BUG_REPORT.yml 2024-09-13 11:40:00 +02:00
Andras Bacsai
b6080c2c8e Merge branch 'next' into feat--terminal-pty 2024-09-13 11:25:47 +02:00
Andras Bacsai
1738286983 chore: Update shared.php to fix issues with source and network variables 2024-09-13 11:12:28 +02:00
peaklabs-dev
703bf51705 Update BUG_REPORT.yml 2024-09-13 11:00:00 +02:00
Andras Bacsai
aa4980289d feat: make coolify full width by default 2024-09-13 10:51:51 +02:00
Andras Bacsai
dd8a2dd3c1 chore: Update coolify environment variable assignment with double quotes 2024-09-13 08:23:05 +02:00
lasse
3086ef1462 Update actions.blade.php 2024-09-12 21:34:52 +02:00
peaklabs-dev
b9b33c831c Fix: Made help text more clear 2024-09-12 17:17:19 +02:00
peaklabs-dev
9310af0301 Feat: New cf tunnel install flow 2024-09-12 15:50:22 +02:00
peaklabs-dev
0d3d5f40fd Fix: Css issue with advanced settings and remove cf tunnel in onboarding 2024-09-12 14:58:52 +02:00
peaklabs-dev
894f2c66d3 Merge branch 'coollabsio:main' into add-debugbar 2024-09-12 13:03:56 +02:00
Luan Estradioto
35dfb1b0f8 fix: grouped process and docker execs are killed with ssh process
fix: run clear command only if exists
fix: link terminal js on dev compose better dx
fix: add error on terminal ux
2024-09-12 01:58:56 -03:00
Luan Estradioto
2edcd01493 Merge remote-tracking branch 'upstream/feat--terminal-pty' into feat--terminal-pty 2024-09-11 20:41:30 -03:00
peaklabs-dev
baa236f934 Feat: Add debug bar 2024-09-11 16:46:14 +02:00
Andras Bacsai
117fbeb07c fixes for terminal 2024-09-11 12:19:27 +02:00
Andras Bacsai
33e9c9b0f9 Merge branch 'next' into feat--terminal-pty 2024-09-11 10:41:33 +02:00
ayntk-ai
fc3c69f687 Feat: more conformations and fixes 2024-09-05 17:54:32 +02:00
ayntk-ai
08df814408 Feat: delete volume confirmation 2024-09-04 22:33:47 +02:00
ayntk-ai
a7b78dcf41 Feat: Backup job confirmation 2024-09-04 21:29:32 +02:00
ayntk-ai
2b5df8d2fd Feat: Team deletion confirmation 2024-09-04 21:15:46 +02:00
ayntk-ai
f4263ee022 Feat: User deletion confirmation 2024-09-04 21:14:18 +02:00
ayntk-ai
44f3f6001e Feat: Redeploy all confirmation 2024-09-04 21:04:43 +02:00
ayntk-ai
9105c1aa51 Feat: GH app deletion confirmation 2024-09-04 21:00:58 +02:00
ayntk-ai
3e04a7958e Feat/Fix: Proxy stop and restart confirmation 2024-09-04 20:41:17 +02:00
ayntk-ai
505127dae5 Feat: confirm server settings 2024-09-04 20:11:04 +02:00
ayntk-ai
371fe53911 Feat: confirm server deletion 2024-09-04 20:06:22 +02:00
ayntk-ai
9515bc6162 Feat: confirm private key 2024-09-04 20:02:01 +02:00
ayntk-ai
93a4a3e09c Feat: confirm API token 2024-09-04 19:58:36 +02:00
ayntk-ai
bbbd5cbaa1 Feat: confirm scheduled tasks 2024-09-04 19:44:10 +02:00
ayntk-ai
7fe3b78d45 Feat: Environment variabel deletion 2024-09-04 19:41:10 +02:00
ayntk-ai
a29353c1ae Feat: confirm ressource operation 2024-09-04 19:34:47 +02:00
ayntk-ai
c16e914be4 Fix: DB image cleanup 2024-09-04 14:59:44 +02:00
ayntk-ai
bec974dca4 Fix application image cleanup 2024-09-04 14:54:43 +02:00
ayntk-ai
b314b08f25 Feat: stop service confirm 2024-09-04 14:34:46 +02:00
ayntk-ai
9a2d5be354 Feat: confirm file storage 2024-09-03 18:31:06 +02:00
ayntk-ai
d94e39ccc7 Feat: service confirmation 2024-09-03 16:59:51 +02:00
ayntk-ai
d5b7e9ed83 Feat: preview deployments and typos 2024-09-03 16:43:50 +02:00
ayntk-ai
f29bc52fa5 Feat: general confirm 2024-09-03 15:59:30 +02:00
ayntk-ai
3d21f1a2a4 fix checkbox hide label 2024-09-03 14:59:01 +02:00
ayntk-ai
20558d438a remove ray 2024-09-03 00:30:15 +02:00
ayntk-ai
792f6bc163 made wording more clear 2024-09-03 00:01:24 +02:00
ayntk-ai
3d1c730703 Feat: del init script 2024-09-02 23:58:48 +02:00
ayntk-ai
c3188958b4 Feat: DB start, stop confirm 2024-09-02 23:54:16 +02:00
ayntk-ai
c24fec9c45 fix step and password error 2024-09-02 23:48:05 +02:00
ayntk-ai
70043c24cf 100000000x Speed improvement first toast then submit in the background 2024-09-02 23:30:47 +02:00
ayntk-ai
ff1e08cf8b add toast dispatching 2024-09-02 23:25:18 +02:00
ayntk-ai
a4d1ae1341 Feat: ability to hide labels 2024-09-02 21:54:21 +02:00
ayntk-ai
5944ee5524 fix close modal on submit 2024-09-02 21:22:31 +02:00
ayntk-ai
1b0c5f8d69 css fix for long text 2024-09-02 19:49:57 +02:00
ayntk-ai
dfd218ec06 fixes here and there 2024-09-02 19:27:21 +02:00
ayntk-ai
843e3fb599 fix checkboxes in danger 2024-08-31 21:10:44 +02:00
ayntk-ai
a97ccd206c confirm init script 2024-08-31 20:55:56 +02:00
ayntk-ai
776d41613d Fix 3 risk levels 2024-08-31 20:55:43 +02:00
ayntk-ai
f857bbc437 fix submit action 2024-08-31 19:31:23 +02:00
ayntk-ai
f8226cf892 confirm backup deletion 2024-08-31 19:31:01 +02:00
ayntk-ai
d2a0621f93 delete backup folder if empty 2024-08-31 18:38:57 +02:00
ayntk-ai
38845d7eb0 confirm backup and delete all backups of the job 2024-08-31 18:29:19 +02:00
ayntk-ai
3b3bc6c33b fix default checkbox state false or true 2024-08-31 16:19:39 +02:00
ayntk-ai
2adac01034 confirm danger 2024-08-31 16:12:08 +02:00
ayntk-ai
b656cabb33 delete project confirmation 2024-08-31 15:07:50 +02:00
ayntk-ai
830c047ccf delete environment confirmation 2024-08-31 15:06:07 +02:00
ayntk-ai
a3dd48de1d destination and dashboard confirmation 2024-08-31 14:51:59 +02:00
ayntk-ai
b118a627d0 fix password validation and password error 2024-08-31 13:41:08 +02:00
ayntk-ai
bcfca40f3a rest checkboxes on close 2024-08-31 13:10:45 +02:00
ayntk-ai
76cb473db8 fix default checkbox state 2024-08-31 13:06:55 +02:00
ayntk-ai
73dfdb83a7 fix rendering bug, more props 2024-08-31 12:55:17 +02:00
ayntk-ai
bff6964d4a scheduled task deletion 2024-08-30 20:33:12 +02:00
ayntk-ai
b807601d19 delete user confirm 2024-08-30 20:08:14 +02:00
ayntk-ai
9136d7acdc WIP more delete confirmations 2024-08-30 20:00:04 +02:00
ayntk-ai
da0398f35d remove unused code 2024-08-29 21:47:17 +02:00
ayntk-ai
6820fcc084 Fix name for services and DBs 2024-08-29 18:53:49 +02:00
ayntk-ai
d984bec175 Ajust text 2024-08-29 18:08:58 +02:00
ayntk-ai
182af1ec18 Fix application delete 2024-08-29 18:01:16 +02:00
ayntk-ai
a22e757ab7 fix execute action on the last button 2024-08-29 15:19:19 +02:00
ayntk-ai
354c74e920 fix 2024-08-29 12:37:19 +02:00
ayntk-ai
62b7900855 more props and more fixes 2024-08-28 15:40:39 +02:00
ayntk-ai
c92659994d improve button text 2024-08-28 13:41:24 +02:00
ayntk-ai
a2651ab3c1 reset modal on cancel, fix back button when there is no prior step 2024-08-28 13:38:37 +02:00
ayntk-ai
1b51d46b3d more props, nav button fixes 2024-08-28 13:31:09 +02:00
ayntk-ai
141752b9ad fix checkbox actions default display 2024-08-28 13:11:42 +02:00
ayntk-ai
73068aaa75 Refactor: Integrate tow step process in the modal component WIP 2024-08-28 12:52:04 +02:00
ayntk-ai
8d2a02dc3c change title of confirmation popup 2024-08-27 14:29:47 +02:00
ayntk-ai
4726676248 make things more clear 2024-08-27 14:19:37 +02:00
ayntk-ai
9040f5d2a1 confirm with password 2024-08-27 13:44:12 +02:00
ayntk-ai
ac50d8b4d8 fix styling 2024-08-27 12:57:03 +02:00
ayntk-ai
2a581147aa new confirm delete dialog 2024-08-27 12:43:59 +02:00
Franck Kerbiriou
7e154ba3c3 Add Mailpit template 2024-08-22 22:52:20 +02:00
Luan Estradioto
2b8c9920d8 removed extra container and added new process to soketi container 2024-08-15 20:52:50 -03:00
Luan Estradioto
548fc21e40 added ws authentication 2024-08-15 11:17:18 -03:00
Luan Estradioto
c2ea8996ee feat: fully functional terminal for command center 2024-08-15 11:17:18 -03:00
ayntk-ai
5b54dc8792 Revert "improve CleanupDocker.php"
This reverts commit b5360e5e75.
2024-08-09 23:25:57 +02:00
ayntk-ai
b5360e5e75 improve CleanupDocker.php 2024-08-09 23:12:30 +02:00
ayntk-ai
840e225aa8 formatting and waring text 2024-08-09 22:43:18 +02:00
ayntk-ai
7d1179e7c8 fix cleanup images for databases 2024-08-09 22:25:39 +02:00
ayntk-ai
16a5c601e3 graceful db stop and db deletion fixes 2024-08-09 22:15:45 +02:00
ayntk-ai
c566152f37 improve graceful_shutdown_container 2024-08-09 20:28:57 +02:00
ayntk-ai
2ca6ffb84e fix public function service.php 2024-08-09 19:57:53 +02:00
ayntk-ai
41be1f7666 use public function 2024-08-09 19:39:43 +02:00
ayntk-ai
450351921e added public functions 2024-08-09 19:39:21 +02:00
ayntk-ai
a4bb87d13b simplify DeleteService.php 2024-08-09 19:22:14 +02:00
ayntk-ai
1cfddfd529 fix stop large amount of containers 2024-08-09 19:17:58 +02:00
ayntk-ai
72bcf03cbb graceful service container stop 2024-08-09 18:59:41 +02:00
ayntk-ai
d177e49e62 updated warning message and formating 2024-08-09 18:43:13 +02:00
ayntk-ai
5595853379 WIP database network, image removal 2024-08-09 03:03:40 +02:00
ayntk-ai
97c2bedda2 add delete_connected_networks function to services.php 2024-08-09 03:00:30 +02:00
ayntk-ai
d980c7a425 only run network removal on stop service if it is not a deletion operation 2024-08-09 02:59:41 +02:00
ayntk-ai
53dff4ca4f simplify uuid variabel 2024-08-09 02:58:59 +02:00
ayntk-ai
7722809c52 typo 2024-08-09 02:20:32 +02:00
ayntk-ai
e67e03f73f added comments and removed temp ones 2024-08-09 02:15:40 +02:00
ayntk-ai
86a087056e fix volume deletion for services 2024-08-09 02:11:42 +02:00
ayntk-ai
51071da700 fix order 2024-08-09 01:00:07 +02:00
ayntk-ai
70aa05bde9 order in importance 2024-08-09 00:42:23 +02:00
ayntk-ai
0135e2b5c0 add logic 2024-08-09 00:30:11 +02:00
ayntk-ai
2f95349888 Merge branch 'coollabsio:main' into fix-#2546-deletion-issues 2024-08-08 15:40:13 +02:00
ayntk-ai
4d0acee95c UI options for deletion WIP 2024-08-08 12:31:37 +02:00
ayntk-ai
74bea37b43 Merge branch 'coollabsio:main' into fix-#2546-deletion-issues 2024-08-08 11:50:57 +02:00
ayntk-ai
070daee28e remove networks and cleanup unused images when stoping dockercompose build pack containers 2024-08-08 01:19:17 +02:00
ayntk-ai
df796dffa2 fix delte networks and unused images of services when deleted 2024-08-08 01:02:48 +02:00
Luan Estradioto
1138ec0dea add storage:link to dev environment 2024-07-21 13:30:28 -03:00
Alejandro Akbal
21fc8efb86 feat: add Mixpost template 2024-06-04 13:13:37 +01:00
329 changed files with 6795 additions and 3410 deletions

View File

@@ -6,7 +6,7 @@ APP_KEY=
APP_URL=http://localhost
APP_PORT=8000
APP_DEBUG=true
SSH_MUX_ENABLED=false
SSH_MUX_ENABLED=true
# PostgreSQL Database Configuration
DB_DATABASE=coolify
@@ -19,11 +19,7 @@ DB_PORT=5432
# Set to true to enable Ray
RAY_ENABLED=false
# Set custom ray port
RAY_PORT=
# Clockwork Configuration
CLOCKWORK_ENABLED=false
CLOCKWORK_QUEUE_COLLECT=true
# RAY_PORT=
# Enable Laravel Telescope for debugging
TELESCOPE_ENABLED=false

View File

@@ -0,0 +1,65 @@
name: 🐞 Bug Report
description: "File a new bug report."
title: "[Bug]: "
labels: ["🐛 Bug", "🔍 Triage"]
body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.)
# 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
- If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new).
- type: textarea
attributes:
label: Error Message and Logs
description: Provide a detailed description of the error or exception you encountered, along with any relevant log output.
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: Please provide a step-by-step guide to reproduce the issue. Be as detailed as possible, otherwise we may not be able to assist you.
value: |
1.
2.
3.
4.
validations:
required: true
- type: input
attributes:
label: Example Repository URL
description: If applicable, provide a URL to a repository demonstrating the issue.
- type: input
attributes:
label: Coolify Version
description: Please provide the Coolify version you are using. This can be found in the top left corner of your Coolify dashboard.
placeholder: "v4.0.0-beta.335"
validations:
required: true
- type: dropdown
attributes:
label: Are you using Coolify Cloud?
options:
- "No (self-hosted)"
- "Yes (Coolify Cloud)"
validations:
required: true
- type: input
attributes:
label: Operating System and Version (self-hosted)
description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version.
placeholder: "Ubuntu 22.04"
- type: textarea
attributes:
label: Additional Information
description: Any other relevant details about the issue.

View File

@@ -0,0 +1,31 @@
name: 💎 Enhancement Bounty
description: "Propose a new feature, service, or improvement with an attached bounty."
title: "[Enhancement]: "
labels: ["✨ Enhancement", "🔍 Triage"]
body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions).
# 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new))
- [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new)
- type: dropdown
attributes:
label: Request Type
description: Select the type of request you are making.
options:
- New Feature
- New Service
- Improvement
validations:
required: true
- type: textarea
attributes:
label: Description
description: Provide a detailed description of the feature, improvement, or service you are proposing.
validations:
required: true

View File

@@ -1,46 +0,0 @@
name: Bug report
description: "Create a new bug report."
title: "[Bug]: "
body:
- type: markdown
attributes:
value: >-
# 💎 Bounty program (with
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
If you would like to prioritize the issue resolution, you can add bounty
to this issue.
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
get started.
- type: textarea
attributes:
label: Description
description: A clear and concise description of the problem
- type: textarea
attributes:
label: Minimal Reproduction (if possible, example repository)
description: Please provide a step by step guide to reproduce the issue.
validations:
required: true
- type: textarea
attributes:
label: Exception or Error
description: Please provide error logs if possible.
- type: input
attributes:
label: Version
description: Coolify's version (see top of your screen).
validations:
required: true
- type: checkboxes
attributes:
label: Cloud?
description: "Are you using the cloud version of Coolify?"
options:
- label: 'Yes'
required: false
- label: 'No'
required: false

View File

@@ -1,8 +1,18 @@
blank_issues_enabled: false
contact_links:
- name: 🤔 Community Support (Chat)
- name: 🤔 Questions and Community Support
url: https://coollabs.io/discord
about: Reach out to us on Discord.
- name: 🙋‍♂️ Feature Requests
url: https://github.com/coollabsio/coolify/discussions/categories/new-features
about: All feature requests will be discussed here.
about: If you have any questions, reach out to us on Discord inside the "#support" channel.
- name: 💡 Feature Request
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests
about: Suggest a new feature for Coolify.
- name: ⚙️ Service Request
url: https://github.com/coollabsio/coolify/discussions/categories/service-requests
about: Request a new service integration for Coolify.
- name: 🔧 Improvements
url: https://github.com/coollabsio/coolify/discussions/categories/improvements
about: Suggest improvements to existing features for Coolify.

View File

@@ -1 +1,13 @@
> Always use `next` branch as destination branch for PRs, not `main`
## Submit Checklist (REMOVE THIS SECTION BEFORE SUBMITTING)
- [ ] I have selected the `next` branch as the destination for my PR, not `main`.
- [ ] I have listed all changes in the `Changes` section.
- [ ] I have filled out the `Issues` section with the issue/discussion link(s) (if applicable).
- [ ] I have tested my changes.
- [ ] I have considered backwards compatibility.
- [ ] I have removed this checklist and any unused sections.
## Changes
-
## Issues
- fix #

View File

@@ -2,7 +2,7 @@ name: Coolify Helper Image (v4)
on:
push:
branches: [ "main", "next" ]
branches: [ "main" ]
paths:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile

View File

@@ -1,17 +1,18 @@
name: PR Build (v4)
name: Coolify Realtime Development (v4)
on:
pull_request:
types:
- opened
branches-ignore: ["main", "v3"]
paths-ignore:
- .github/workflows/coolify-helper.yml
- docker/coolify-helper/Dockerfile
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
amd64:
@@ -19,9 +20,6 @@ jobs:
permissions:
contents: read
packages: write
attestations: write
id-token: write
actions: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
@@ -30,22 +28,26 @@ jobs:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/prod/Dockerfile
file: docker/coolify-realtime/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
attestations: write
id-token: write
actions: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
@@ -54,22 +56,26 @@ jobs:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/prod/Dockerfile
file: docker/coolify-realtime/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
actions: write
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
@@ -84,10 +90,14 @@ jobs:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

103
.github/workflows/coolify-realtime.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Coolify Realtime (v4)
on:
push:
branches: [ "main" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
amd64:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}

View File

@@ -1,44 +0,0 @@
name: Docker Image CI
on:
# push:
# branches: [ "main" ]
# pull_request:
# branches: [ "*" ]
push:
branches: ["this-does-not-exist"]
pull_request:
branches: ["this-does-not-exist"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: |
/usr/local/share/ca-certificates
/var/cache/apt/archives
/var/lib/apt/lists
~/.cache
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}
restore-keys: |
${{ runner.os }}-docker-
- name: Build the Docker image
run: |
cp .env.example .env
docker run --rm -u "$(id -u):$(id -g)" \
-v "$(pwd):/app" \
-w /app composer:2 \
composer install --ignore-platform-reqs
./vendor/bin/spin build
- name: Start the stack
run: |
./vendor/bin/spin up -d
./vendor/bin/spin exec coolify php artisan key:generate
./vendor/bin/spin exec coolify php artisan migrate:fresh --seed
- name: Test (missing E2E tests)
run: |
./vendor/bin/spin exec coolify php artisan test

View File

@@ -1,25 +0,0 @@
name: Fix PHP code style issues
on: [push]
permissions:
contents: write
jobs:
php-code-styling:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Fix PHP code style issues
uses: aglipanci/laravel-pint-action@2.4
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling

View File

@@ -0,0 +1,17 @@
name: Lock closed Issues, Discussions, and PRs
on:
schedule:
- cron: '0 1 * * *'
jobs:
lock-threads:
runs-on: ubuntu-latest
steps:
- name: Lock threads after 30 days of inactivity
uses: dessant/lock-threads@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '30'
pr-inactive-days: '30'
discussion-inactive-days: '30'

View File

@@ -0,0 +1,28 @@
name: Manage Stale Issues and PRs
on:
schedule:
- cron: '0 2 * * *'
jobs:
manage-stale:
runs-on: ubuntu-latest
steps:
- name: Manage stale issues and PRs
uses: actions/stale@v9
id: stale
with:
stale-issue-message: 'This issue will be automatically closed in a few days if no response is received. Please provide an update with the requested information.'
stale-pr-message: 'This pull request will be automatically closed in a few days if no response is received. Please update your PR or comment if you would like to continue working on it.'
close-issue-message: 'This issue has been automatically closed due to inactivity.'
close-pr-message: 'This pull request has been automatically closed due to inactivity.'
days-before-stale: 14
days-before-close: 7
stale-issue-label: '⏱︎ Stale'
stale-pr-label: '⏱︎ Stale'
only-labels: '💤 Waiting for feedback'
remove-stale-when-updated: true
operations-per-run: 100
labels-to-remove-when-unstale: '⏱︎ Stale, 💤 Waiting for feedback'
close-issue-reason: 'not_planned'
exempt-all-milestones: false

View File

@@ -0,0 +1,78 @@
name: Remove Labels and Assignees on Issue Close
on:
issues:
types: [closed]
pull_request:
types: [closed]
pull_request_target:
types: [closed]
jobs:
remove-labels-and-assignees:
runs-on: ubuntu-latest
steps:
- name: Remove labels and assignees
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
async function processIssue(issueNumber) {
try {
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: issueNumber
});
const labelsToKeep = currentLabels
.filter(label => label.name === '⏱︎ Stale')
.map(label => label.name);
await github.rest.issues.setLabels({
owner,
repo,
issue_number: issueNumber,
labels: labelsToKeep
});
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number: issueNumber
});
if (issue.assignees && issue.assignees.length > 0) {
await github.rest.issues.removeAssignees({
owner,
repo,
issue_number: issueNumber,
assignees: issue.assignees.map(assignee => assignee.login)
});
}
} catch (error) {
if (error.status !== 404) {
console.error(`Error processing issue ${issueNumber}:`, error);
}
}
}
if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
const issue = context.payload.issue || context.payload.pull_request;
await processIssue(issue.number);
}
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
const pr = context.payload.pull_request;
if (pr.body) {
const issueReferences = pr.body.match(/#(\d+)/g);
if (issueReferences) {
for (const reference of issueReferences) {
const issueNumber = parseInt(reference.substring(1));
await processIssue(issueNumber);
}
}
}
}

View File

@@ -12,9 +12,10 @@ You can ask for guidance anytime on our [Discord server](https://coollabs.io/dis
4. [Set up Environment Variables](#4-set-up-environment-variables)
5. [Start Coolify](#5-start-coolify)
6. [Start Development](#6-start-development)
7. [Development Notes](#7-development-notes)
8. [Create a Pull Request](#8-create-a-pull-request)
9. [Additional Contribution Guidelines](#additional-contribution-guidelines)
7. [Create a Pull Request](#7-create-a-pull-request)
8. [Development Notes](#development-notes)
9. [Resetting Development Environment](#resetting-development-environment)
10. [Additional Contribution Guidelines](#additional-contribution-guidelines)
## 1. Setup Development Environment
@@ -25,15 +26,15 @@ Follow the steps below for your operating system:
1. Install `docker-ce`, Docker Desktop (or similar):
- Docker CE (recommended):
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/)
- Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install?ref=coolify)
- After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/?ref=coolify)
- Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide
- Install Docker Desktop (easier):
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/)
- Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/?ref=coolify)
- Ensure WSL2 backend is enabled in Docker Desktop settings
2. Install Spin:
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2)
- Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2?ref=coolify)
</details>
@@ -42,12 +43,12 @@ Follow the steps below for your operating system:
1. Install Orbstack, Docker Desktop (or similar):
- Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop):
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation)
- Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation?ref=coolify)
- Docker Desktop:
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/)
- Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/?ref=coolify)
2. Install Spin:
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin)
- Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin?ref=coolify)
</details>
@@ -56,12 +57,12 @@ Follow the steps below for your operating system:
1. Install Docker Engine, Docker Desktop (or similar):
- Docker Engine (recommended, as there is no VM overhead):
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/) for your Linux distribution
- Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/?ref=coolify) for your Linux distribution
- Docker Desktop:
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/)
- If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/?ref=coolify)
2. Install Spin:
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions)
- Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions?ref=coolify)
</details>
@@ -85,14 +86,14 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
| Editor | Platform | Download Link |
|--------|----------|---------------|
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) |
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) |
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) |
| Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download?ref=coolify) |
| Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/?ref=coolify) |
| Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download?ref=coolify) |
3. Clone the Coolify Repository from your fork to your local machine
- Use `git clone` in the command line, or
- Use GitHub Desktop (recommended):
- Download and install from [https://desktop.github.com/](https://desktop.github.com/)
- Download and install from [https://desktop.github.com/](https://desktop.github.com/?ref=coolify)
- Open GitHub Desktop and login with your GitHub account
- Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone`
@@ -145,7 +146,36 @@ After installing Docker (or Orbstack) and Spin, verify the installation:
> TELESCOPE_ENABLED=true
> ```
## 7. Development Notes
## 7. Create a Pull Request
1. After making changes or adding a new service:
- Commit your changes to your forked repository.
- Push the changes to your GitHub account.
2. Creating the Pull Request (PR):
- Navigate to the main Coolify repository on GitHub.
- Click the "Pull requests" tab.
- Click the green "New pull request" button.
- Choose your fork and branch as the compare branch.
- Click "Create pull request".
3. Filling out the PR details:
- Give your PR a descriptive title.
- Use the Pull Request Template provided and fill in the details.
> [!IMPORTANT]
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
4. Submit your PR:
- Review your changes one last time.
- Click "Create pull request" to submit.
> [!NOTE]
> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
After submission, maintainers will review your PR and may request changes or provide feedback.
## Development Notes
When working on Coolify, keep the following in mind:
@@ -164,35 +194,41 @@ When working on Coolify, keep the following in mind:
> [!IMPORTANT]
> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches.
## 8. Create a Pull Request
## Resetting Development Environment
1. After making changes or adding a new service:
- Commit your changes to your forked repository.
- Push the changes to your GitHub account.
If you encounter issues or break your database or something else, follow these steps to start from a clean slate (works since `v4.0.0-beta.342`):
2. Creating the Pull Request (PR):
- Navigate to the main Coolify repository on GitHub.
- Click the "Pull requests" tab.
- Click the green "New pull request" button.
- Choose your fork and branch as the compare branch.
- Click "Create pull request".
1. Stop all running containers `ctrl + c`.
3. Filling out the PR details:
- Give your PR a descriptive title.
- In the description, explain the changes you've made.
- Reference any related issues by using keywords like "Fixes #123" or "Closes #456".
2. Remove all Coolify containers:
```bash
docker rm coolify coolify-db coolify-redis coolify-realtime coolify-testing-host coolify-minio coolify-vite-1 coolify-mail
```
3. Remove Coolify volumes (it is possible that the volumes have no `coolify` prefix on your machine, in that case remove the prefix from the command):
```bash
docker volume rm coolify_dev_backups_data coolify_dev_postgres_data coolify_dev_redis_data coolify_dev_coolify_data coolify_dev_minio_data
```
4. Remove unused images:
```bash
docker image prune -a
```
5. Start Coolify again:
```bash
spin up
```
6. Run database migrations and seeders:
```bash
docker exec -it coolify php artisan migrate:fresh --seed
```
After completing these steps, you'll have a fresh development setup.
> [!IMPORTANT]
> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch.
4. Submit your PR:
- Review your changes one last time.
- Click "Create pull request" to submit.
> [!NOTE]
> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers.
After submission, maintainers will review your PR and may request changes or provide feedback.
> Always run database migrations and seeders after switching branches or pulling updates to ensure your local database structure matches the current codebase and includes necessary seed data.
## Additional Contribution Guidelines

View File

@@ -2,35 +2,120 @@
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
## Table of Contents
- [Release Process](#release-process)
- [Version Types](#version-types)
- [Stable](#stable)
- [Nightly](#nightly)
- [Beta](#beta)
- [Version Availability](#version-availability)
- [Self-Hosted](#self-hosted)
- [Cloud](#cloud)
- [Manually Update to Specific Versions](#manually-update-to-specific-versions)
## Release Process
1. **Development on `next` or separate branches**
- Changes, fixes and new features are developed on the `next` or even separate branches.
1. **Development on `next` or Feature Branches**
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
2. **Merging to `main`**
- Once changes are ready, they are merged from `next` into the `main` branch.
- Once ready, changes are merged from the `next` branch into the `main` branch.
3. **Building the release**
- After merging to `main`, a new release is built.
- Note: A push to `main` does not automatically mean a new version is released.
3. **Building the Release**
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
4. **Creating a GitHub release**
- A new release is created on GitHub with the new version details.
4. **Creating a GitHub Release**
- A new GitHub release is manually created with details of the changes made in the version.
5. **Updating the CDN**
- The final step is updating the version information on the CDN:
[https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
> [!NOTE]
> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes.
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
## Version Types
<details>
<summary><strong>Stable (coming soon)</strong></summary>
- **Stable**
- The production version suitable for stable, production environments (generally recommended).
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
- **Installation Command:**
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
</details>
<details>
<summary><strong>Nightly</strong></summary>
- **Nightly**
- The latest development version, suitable for testing the latest changes and experimenting with new features.
- **Update Frequency:** Daily or bi-weekly updates.
- **Release Size:** Smaller, more frequent releases.
- **Versioning Scheme:** TO BE DETERMINED
- **Installation Command:**
```bash
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
```
</details>
<details>
<summary><strong>Beta</strong></summary>
- **Beta**
- Test releases for the upcoming stable version.
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
- **Update Frequency:** Available if we think beta testing is necessary.
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
- **Installation Command:**
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
</details>
> [!WARNING]
> Do not use nightly/beta builds in production as there is no guarantee of stability.
## Version Availability
It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update).
When a new version is released and a new GitHub release is created, it doesn't immediately become available for your instance. Here's how version availability works for different instance types.
### Self-Hosted
- **Update Frequency:** More frequent updates, especially on the nightly release channel.
- **Update Availability:** New versions are available once the CDN has been updated.
- **Update Methods:**
1. **Manual Update in Instance Settings:**
- Go to `Settings > Update Check Frequency` and click the `Check Manually` button.
- If an update is available, an upgrade button will appear on the sidebar.
2. **Automatic Update:**
- If enabled, the instance will update automatically at the time set in the settings.
3. **Re-run Installation Script:**
- Run the installation script again to upgrade to the latest version available on the CDN:
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
> [!IMPORTANT]
> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released.
> If a new release is available on GitHub but your instance hasn't updated yet or no upgrade button is shown in the UI, the CDN might not have been updated yet. This intentional delay ensures stability and allows for hotfixes before official release.
### Cloud
- **Update Frequency:** Less frequent as it's a managed service.
- **Update Availability:** New versions are available once Andras has updated the cloud version manually.
- **Update Method:**
- Updates are managed by Andras, who ensures each cloud version is thoroughly tested and stable before releasing it.
> [!IMPORTANT]
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
## Manually Update to Specific Versions
@@ -42,4 +127,4 @@ To update your Coolify instance to a specific (unreleased) version, use the foll
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
```
-> Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).
Replace `<version>` with the version you want to update to (for example `4.0.0-beta.332`).

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Application;
use App\Actions\Server\CleanupDocker;
use App\Models\Application;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -9,44 +10,35 @@ class StopApplication
{
use AsAction;
public function handle(Application $application, bool $previewDeployments = false)
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true)
{
if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
try {
$server = $application->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
ray('Stopping application: '.$application->name);
if ($server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $server);
return;
}
$servers = collect([]);
$servers->push($application->destination->server);
$application->additional_servers->map(function ($server) use ($servers) {
$servers->push($server);
});
foreach ($servers as $server) {
if (! $server->isFunctional()) {
return 'Server is not functional';
}
if ($previewDeployments) {
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
}
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
}
}
}
$containersToStop = $application->getContainersToStop($previewDeployments);
$application->stopContainers($containersToStop, $server);
if ($application->build_pack === 'dockercompose') {
// remove network
$uuid = $application->uuid;
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
instant_remote_process(["docker network rm {$uuid}"], $server, false);
}
$application->delete_connected_networks($application->uuid);
}
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Actions\CoolifyTask;
use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus;
use App\Helpers\SshMultiplexingHelper;
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Server;
use Illuminate\Process\ProcessResult;
@@ -137,7 +138,7 @@ class RunRemoteProcess
$command = $this->activity->getExtraProperty('command');
$server = Server::whereUuid($server_uuid)->firstOrFail();
return generateSshCommand($server, $command);
return SshMultiplexingHelper::generateSshCommand($server, $command);
}
protected function handleOutput(string $type, string $output)

View File

@@ -46,9 +46,6 @@ class StartDragonfly
'networks' => [
$this->database->destination->network,
],
'ulimits' => [
'memlock' => '-1',
],
'labels' => [
'coolify.managed' => 'true',
],

View File

@@ -37,7 +37,6 @@ class StartPostgresql
$this->generate_init_scripts();
$this->add_custom_conf();
$docker_compose = [
'services' => [
$container_name => [

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database;
use App\Actions\Server\CleanupDocker;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
@@ -10,25 +11,65 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Process;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true)
{
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
$this->stopContainer($database, $database->uuid, 300);
if (! $isDeleteOperation) {
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
}
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
return 'Database stopped successfully';
}
private function stopContainer($database, string $containerName, int $timeout = 300): void
{
$server = $database->destination->server;
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
$startTime = time();
while ($process->running()) {
if (time() - $startTime >= $timeout) {
$this->forceStopContainer($containerName, $server);
break;
}
usleep(100000);
}
$this->removeContainer($containerName, $server);
}
private function forceStopContainer(string $containerName, $server): void
{
instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false);
}
private function removeContainer(string $containerName, $server): void
{
instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false);
}
private function deleteConnectedNetworks($uuid, $server)
{
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
instant_remote_process(["docker network rm {$uuid}"], $server, false);
}
}

View File

@@ -543,7 +543,7 @@ class GetContainersStatus
}
}
}
$exitedServices = $exitedServices->unique('id');
$exitedServices = $exitedServices->unique('uuid');
foreach ($exitedServices as $exitedService) {
if (str($exitedService->status)->startsWith('exited')) {
continue;
@@ -651,8 +651,9 @@ class GetContainersStatus
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
$this->server->proxyType();
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
return;
}
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Fortify;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
@@ -20,7 +19,7 @@ class CreateNewUser implements CreatesNewUsers
*/
public function create(array $input): User
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403);
}
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
$team = $user->teams()->first();
// Disable registration after first user is created
$settings = InstanceSettings::get();
$settings = instanceSettings();
$settings->is_registration_enabled = false;
$settings->save();
} else {

View File

@@ -2,7 +2,6 @@
namespace App\Actions\License;
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,7 +12,7 @@ class CheckResaleLicense
public function handle()
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (isDev()) {
$settings->update([
'is_resale_license_active' => true,

View File

@@ -26,7 +26,7 @@ class CheckProxy
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
return false;
}
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();
['uptime' => $uptime, 'error' => $error] = $server->validateConnection(false);
if (! $uptime) {
throw new \Exception($error);
}

View File

@@ -35,7 +35,7 @@ class StartProxy
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
'docker stack deploy -c docker-compose.yml coolify-proxy',
"echo 'Proxy started successfully.'",
"echo 'Successfully started coolify-proxy.'",
]);
} else {
$caddfile = 'import /dynamic/*.caddy';
@@ -46,11 +46,14 @@ class StartProxy
"echo 'Creating required Docker Compose file.'",
"echo 'Pulling docker image.'",
'docker compose pull',
"echo 'Stopping existing coolify-proxy.'",
'docker compose down -v --remove-orphans > /dev/null 2>&1',
'if docker ps -a --format "{{.Names}}" | grep -q "^coolify-proxy$"; then',
" echo 'Stopping and removing existing coolify-proxy.'",
' docker rm -f coolify-proxy || true',
" echo 'Successfully stopped and removed existing coolify-proxy.'",
'fi',
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'",
"echo 'Successfully started coolify-proxy.'",
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}

View File

@@ -2,7 +2,6 @@
namespace App\Actions\Server;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -12,28 +11,29 @@ class CleanupDocker
public function handle(Server $server)
{
$settings = instanceSettings();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";
$commands = $this->getCommands();
$commands = [
'docker container prune -f --filter "label=coolify.managed=true" --filter "label!=coolify.proxy=true"',
'docker image prune -af --filter "label!=coolify.managed=true"',
'docker builder prune -af',
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi -f",
];
$serverSettings = $server->settings;
if ($serverSettings->delete_unused_volumes) {
$commands[] = 'docker volume prune -af';
}
if ($serverSettings->delete_unused_networks) {
$commands[] = 'docker network prune -f';
}
foreach ($commands as $command) {
instant_remote_process([$command], $server, false);
}
}
private function getCommands(): array
{
$settings = InstanceSettings::get();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion;
$commonCommands = [
'docker container prune -f --filter "label=coolify.managed=true"',
'docker image prune -af --filter "label!=coolify.managed=true"',
'docker builder prune -af',
"docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi",
];
return $commonCommands;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Server;
use App\Events\CloudflareTunnelConfigured;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
@@ -40,12 +41,17 @@ class ConfigureCloudflared
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
ray($e);
$server->settings->is_cloudflare_tunnel = false;
$server->settings->save();
throw $e;
} finally {
CloudflareTunnelConfigured::dispatch($server->team_id);
$commands = collect([
'rm -fr /tmp/cloudflared',
]);
instant_remote_process($commands, $server);
}
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Actions\Server;
use App\Models\InstanceSettings;
use App\Jobs\PullHelperImageJob;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -19,7 +19,7 @@ class UpdateCoolify
public function handle($manual_update = false)
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
return;
@@ -55,6 +55,13 @@ class UpdateCoolify
return;
}
$all_servers = Server::all();
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
PullHelperImageJob::dispatch($server);
}
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
remote_process([

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Service;
use App\Actions\Server\CleanupDocker;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -9,11 +10,11 @@ class DeleteService
{
use AsAction;
public function handle(Service $service)
public function handle(Service $service, bool $deleteConfigurations, bool $deleteVolumes, bool $dockerCleanup, bool $deleteConnectedNetworks)
{
try {
$server = data_get($service, 'server');
if ($server->isFunctional()) {
if ($deleteVolumes && $server->isFunctional()) {
$storagesToDelete = collect([]);
$service->environment_variables()->delete();
@@ -33,13 +34,29 @@ class DeleteService
foreach ($storagesToDelete as $storage) {
$commands[] = "docker volume rm -f $storage->name";
}
$commands[] = "docker rm -f $service->uuid";
instant_remote_process($commands, $server, false);
// Execute volume deletion first, this must be done first otherwise volumes will not be deleted.
if (! empty($commands)) {
foreach ($commands as $command) {
$result = instant_remote_process([$command], $server, false);
if ($result !== 0) {
ray("Failed to execute: $command");
}
}
}
}
if ($deleteConnectedNetworks) {
$service->delete_connected_networks($service->uuid);
}
instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
} finally {
if ($deleteConfigurations) {
$service->delete_configurations();
}
foreach ($service->applications()->get() as $application) {
$application->forceDelete();
}
@@ -50,6 +67,11 @@ class DeleteService
$task->delete();
}
$service->tags()->detach();
$service->forceDelete();
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
}
}
}

View File

@@ -31,7 +31,7 @@ class StartService
$network = $service->destination->network;
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
foreach ($serviceNames as $serviceName => $serviceConfig) {
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true";
}
}
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Service;
use App\Actions\Server\CleanupDocker;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -9,40 +10,27 @@ class StopService
{
use AsAction;
public function handle(Service $service)
public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true)
{
try {
$server = $service->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
ray('Stopping service: '.$service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
if ($applications->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
}
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
if ($dbs->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$containersToStop = $service->getContainersToStop();
$service->stopContainers($containersToStop, $server);
if (! $isDeleteOperation) {
$service->delete_connected_networks($service->uuid);
if ($dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$db->update(['status' => 'exited']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CheckApplicationDeploymentQueue extends Command
{
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
protected $description = 'Check application deployment queue.';
public function handle()
{
$seconds = $this->option('seconds');
$deployments = ApplicationDeploymentQueue::whereIn('status', [
ApplicationDeploymentStatus::IN_PROGRESS,
ApplicationDeploymentStatus::QUEUED,
])->where('created_at', '>=', now()->subSeconds($seconds))->get();
if ($deployments->isEmpty()) {
$this->info('No deployments found in the last '.$seconds.' seconds.');
return;
}
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
foreach ($deployments as $deployment) {
if ($this->option('force')) {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
$this->cancelDeployment($deployment);
} else {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
if ($this->confirm('Do you want to cancel this deployment?', true)) {
$this->cancelDeployment($deployment);
}
}
}
}
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
if ($deployment->server?->isFunctional()) {
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
}
}
}

View File

@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $signature = 'cleanup:deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
protected $description = 'Cleanup application deployment queue.';
public function handle()
{

View File

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

View File

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

View File

@@ -2,10 +2,13 @@
namespace App\Console\Commands;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -35,6 +38,27 @@ class CleanupStuckedResources extends Command
private function cleanup_stucked_resources()
{
try {
$servers = Server::all()->filter(function ($server) {
return $server->isFunctional();
});
foreach ($servers as $server) {
CleanupHelperContainersJob::dispatch($server);
}
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
try {
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
if (is_null($applicationDeploymentQueue->application)) {
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
$applicationDeploymentQueue->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
}
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {

View File

@@ -48,6 +48,13 @@ class Dev extends Command
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
// Generate STORAGE link if not exists
if (! file_exists(public_path('storage'))) {
echo "Generating STORAGE link.\n";
Artisan::call('storage:link');
}
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (! $settings) {

View File

@@ -5,10 +5,8 @@ namespace App\Console\Commands;
use App\Actions\Server\StopSentinel;
use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
@@ -18,7 +16,7 @@ use Illuminate\Support\Facades\Http;
class Init extends Command
{
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
protected $signature = 'app:init {--force-cloud}';
protected $description = 'Cleanup instance related stuffs';
@@ -26,9 +24,63 @@ class Init extends Command
public function handle()
{
if (isCloud() && ! $this->option('force-cloud')) {
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
return;
}
$this->servers = Server::all();
$this->alive();
if (isCloud()) {
} else {
$this->send_alive_signal();
get_public_ips();
}
// Backward compatibility
$this->disable_metrics();
$this->replace_slash_in_environment_name();
$this->restore_coolify_db_backup();
//
$this->update_traefik_labels();
if (! isCloud() || $this->option('force-cloud')) {
$this->cleanup_unused_network_from_coolify_proxy();
}
if (isCloud()) {
$this->cleanup_unnecessary_dynamic_proxy_configuration();
} else {
$this->cleanup_in_progress_application_deployments();
}
$this->call('cleanup:redis');
$this->call('cleanup:stucked-resources');
if (isCloud()) {
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
}
} else {
try {
$localhost = $this->servers->where('id', 0)->first();
$localhost->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = instanceSettings();
if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
}
}
private function disable_metrics()
{
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
foreach ($this->servers as $server) {
if ($server->settings->is_metrics_enabled === true) {
@@ -39,62 +91,6 @@ class Init extends Command
}
}
}
$full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
$this->replace_slash_in_environment_name();
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments();
return;
}
if ($cleanup_proxy_networks) {
echo "Running cleanup proxy networks.\n";
$this->cleanup_unused_network_from_coolify_proxy();
return;
}
if ($full_cleanup) {
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
$this->update_traefik_labels();
$this->cleanup_unused_network_from_coolify_proxy();
$this->cleanup_unnecessary_dynamic_proxy_configuration();
$this->cleanup_in_progress_application_deployments();
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:queue');
$this->call('cleanup:stucked-resources');
if (! isCloud()) {
try {
$localhost = $this->servers->where('id', 0)->first();
$localhost->setupDynamicProxyConfiguration();
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
}
$settings = InstanceSettings::get();
if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
} else {
$settings->update(['is_auto_update_enabled' => false]);
}
}
if (isCloud()) {
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
}
}
return;
}
$this->cleanup_stucked_helper_containers();
$this->call('cleanup:stucked-resources');
}
private function update_traefik_labels()
@@ -108,7 +104,6 @@ class Init extends Command
private function cleanup_unnecessary_dynamic_proxy_configuration()
{
if (isCloud()) {
foreach ($this->servers as $server) {
try {
if (! $server->isFunctional()) {
@@ -128,13 +123,9 @@ class Init extends Command
}
}
}
private function cleanup_unused_network_from_coolify_proxy()
{
if (isCloud()) {
return;
}
foreach ($this->servers as $server) {
if (! $server->isFunctional()) {
continue;
@@ -175,6 +166,7 @@ class Init extends Command
private function restore_coolify_db_backup()
{
if (version_compare('4.0.0-beta.179', config('version'), '<=')) {
try {
$database = StandalonePostgresql::withTrashed()->find(0);
if ($database && $database->trashed()) {
@@ -197,21 +189,13 @@ class Init extends Command
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_helper_containers()
{
foreach ($this->servers as $server) {
if ($server->isFunctional()) {
CleanupHelperContainersJob::dispatch($server);
}
}
}
private function alive()
private function send_alive_signal()
{
$id = config('app.id');
$version = config('version');
$settings = InstanceSettings::get();
$settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
@@ -225,23 +209,7 @@ class Init extends Command
echo "Error in alive: {$e->getMessage()}\n";
}
}
// private function cleanup_ssh()
// {
// TODO: it will cleanup id.root@host.docker.internal
// try {
// $files = Storage::allFiles('ssh/keys');
// foreach ($files as $file) {
// Storage::delete($file);
// }
// $files = Storage::allFiles('ssh/mux');
// foreach ($files as $file) {
// Storage::delete($file);
// }
// } catch (\Throwable $e) {
// echo "Error in cleaning ssh: {$e->getMessage()}\n";
// }
// }
private function cleanup_in_progress_application_deployments()
{
// Cleanup any failed deployments
@@ -263,6 +231,7 @@ class Init extends Command
private function replace_slash_in_environment_name()
{
if (version_compare('4.0.0-beta.298', config('version'), '<=')) {
$environments = Environment::all();
foreach ($environments as $environment) {
if (str_contains($environment->name, '/')) {
@@ -272,3 +241,4 @@ class Init extends Command
}
}
}
}

View File

@@ -12,8 +12,8 @@ use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
@@ -43,6 +43,8 @@ class Kernel extends ConsoleKernel
$schedule->command('uploads:clear')->everyTwoMinutes();
$schedule->command('telescope:prune')->daily();
$schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
@@ -64,7 +66,7 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
@@ -77,16 +79,16 @@ class Kernel extends ConsoleKernel
}
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))
}
$schedule->job(new PullHelperImageJob)
->cron($settings->update_check_frequency)
->timezone($settings->instance_timezone)
->onOneServer();
}
}
private function schedule_updates($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)
@@ -114,6 +116,7 @@ class Kernel extends ConsoleKernel
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
$serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Enums;
enum ContainerStatusTypes: string
{
case PAUSED = 'paused';
case RESTARTING = 'restarting';
case REMOVING = 'removing';
case RUNNING = 'running';
case DEAD = 'dead';
case CREATED = 'created';
case EXITED = 'exited';
}

View File

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

View File

@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
if ($e instanceof RuntimeException) {
return;
}
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
if ($this->settings->do_not_track) {
return;
}

View File

@@ -0,0 +1,186 @@
<?php
namespace App\Helpers;
use App\Models\PrivateKey;
use App\Models\Server;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Process;
class SshMultiplexingHelper
{
public static function serverSshConfiguration(Server $server)
{
$privateKey = PrivateKey::findOrFail($server->private_key_id);
$sshKeyLocation = $privateKey->getKeyLocation();
$muxFilename = '/var/www/html/storage/app/ssh/mux/mux_'.$server->uuid;
return [
'sshKeyLocation' => $sshKeyLocation,
'muxFilename' => $muxFilename,
];
}
public static function ensureMultiplexedConnection(Server $server)
{
if (! self::isMultiplexingEnabled()) {
return;
}
$sshConfig = self::serverSshConfiguration($server);
$muxSocket = $sshConfig['muxFilename'];
$sshKeyLocation = $sshConfig['sshKeyLocation'];
self::validateSshKey($sshKeyLocation);
$checkCommand = "ssh -O check -o ControlPath=$muxSocket ";
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$checkCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
}
$checkCommand .= "{$server->user}@{$server->ip}";
$process = Process::run($checkCommand);
if ($process->exitCode() !== 0) {
self::establishNewMultiplexedConnection($server);
}
}
public static function establishNewMultiplexedConnection(Server $server)
{
$sshConfig = self::serverSshConfiguration($server);
$sshKeyLocation = $sshConfig['sshKeyLocation'];
$muxSocket = $sshConfig['muxFilename'];
$connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval');
$muxPersistTime = config('constants.ssh.mux_persist_time');
$establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" ';
}
$establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval);
$establishCommand .= "{$server->user}@{$server->ip}";
$establishProcess = Process::run($establishCommand);
if ($establishProcess->exitCode() !== 0) {
throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput());
}
}
public static function removeMuxFile(Server $server)
{
$sshConfig = self::serverSshConfiguration($server);
$muxSocket = $sshConfig['muxFilename'];
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket ";
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
}
$closeCommand .= "{$server->user}@{$server->ip}";
Process::run($closeCommand);
}
public static function generateScpCommand(Server $server, string $source, string $dest)
{
$sshConfig = self::serverSshConfiguration($server);
$sshKeyLocation = $sshConfig['sshKeyLocation'];
$muxSocket = $sshConfig['muxFilename'];
$timeout = config('constants.ssh.command_timeout');
$muxPersistTime = config('constants.ssh.mux_persist_time');
$scp_command = "timeout $timeout scp ";
if ($server->isIpv6()) {
$scp_command .= '-6 ';
}
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
}
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$scp_command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
}
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
return $scp_command;
}
public static function generateSshCommand(Server $server, string $command)
{
if ($server->settings->force_disabled) {
throw new \RuntimeException('Server is disabled.');
}
$sshConfig = self::serverSshConfiguration($server);
$sshKeyLocation = $sshConfig['sshKeyLocation'];
$muxSocket = $sshConfig['muxFilename'];
$timeout = config('constants.ssh.command_timeout');
$muxPersistTime = config('constants.ssh.mux_persist_time');
$ssh_command = "timeout $timeout ssh ";
if (self::isMultiplexingEnabled()) {
$ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
}
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
$ssh_command .= "-o ProxyCommand='cloudflared access ssh --hostname %h' ";
}
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
$delimiter = Hash::make($command);
$delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
.$command.PHP_EOL
.$delimiter;
return $ssh_command;
}
private static function isMultiplexingEnabled(): bool
{
return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop');
}
private static function validateSshKey(string $sshKeyLocation): void
{
$checkKeyCommand = "ls $sshKeyLocation 2>/dev/null";
$keyCheckProcess = Process::run($checkKeyCommand);
if ($keyCheckProcess->exitCode() !== 0) {
throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation");
}
}
private static function getCommonSshOptions(Server $server, string $sshKeyLocation, int $connectionTimeout, int $serverInterval, bool $isScp = false): string
{
$options = "-i {$sshKeyLocation} "
.'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
.'-o PasswordAuthentication=no '
."-o ConnectTimeout=$connectionTimeout "
."-o ServerAliveInterval=$serverInterval "
.'-o RequestTTY=no '
.'-o LogLevel=ERROR ';
// Bruh
if ($isScp) {
$options .= "-P {$server->port} ";
} else {
$options .= "-p {$server->port} ";
}
return $options;
}
}

View File

@@ -177,6 +177,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -279,6 +280,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -381,6 +383,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -468,6 +471,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -552,6 +556,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -602,6 +607,7 @@ class ApplicationsController extends Controller
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -627,7 +633,7 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -665,6 +671,7 @@ class ApplicationsController extends Controller
$fqdn = $request->domains;
$instantDeploy = $request->instant_deploy;
$githubAppUuid = $request->github_app_uuid;
$useBuildServer = $request->use_build_server;
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
@@ -737,6 +744,8 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -833,6 +842,8 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->source_type = $githubApp->getMorphClass();
$application->source_id = $githubApp->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -925,6 +936,8 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -1004,6 +1017,8 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1062,6 +1077,8 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1259,16 +1276,10 @@ class ApplicationsController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1316,10 +1327,14 @@ class ApplicationsController extends Controller
'message' => 'Application not found',
], 404);
}
DeleteResourceJob::dispatch(
resource: $application,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Application deletion request queued.',
@@ -1404,6 +1419,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -1460,7 +1476,7 @@ class ApplicationsController extends Controller
], 404);
}
$server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1538,6 +1554,10 @@ class ApplicationsController extends Controller
}
$instantDeploy = $request->instant_deploy;
$use_build_server = $request->use_build_server;
$application->settings->is_build_server_enabled = $use_build_server;
$application->settings->save();
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all();
@@ -2529,6 +2549,131 @@ class ApplicationsController extends Controller
}
#[OA\Post(
summary: 'Execute Command',
description: "Execute a command on the application's current container.",
path: '/applications/{uuid}/execute',
operationId: 'execute-command-application',
security: [
['bearerAuth' => []],
],
tags: ['Applications'],
parameters: [
new OA\Parameter(
name: 'uuid',
in: 'path',
description: 'UUID of the application.',
required: true,
schema: new OA\Schema(
type: 'string',
format: 'uuid',
)
),
],
requestBody: new OA\RequestBody(
required: true,
description: 'Command to execute.',
content: new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'command' => ['type' => 'string', 'description' => 'Command to execute.'],
],
),
),
),
responses: [
new OA\Response(
response: 200,
description: "Execute a command on the application's current container.",
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Command executed.'],
'response' => ['type' => 'string'],
]
)
),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
new OA\Response(
response: 404,
ref: '#/components/responses/404',
),
]
)]
public function execute_command_by_uuid(Request $request)
{
// TODO: Need to review this from security perspective, to not allow arbitrary command execution
$allowedFields = ['command'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['message' => 'UUID is required.'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json(['message' => 'Application not found.'], 404);
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'command' => 'string|required',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
$status = getContainerStatus($application->destination->server, $container['Names']);
if ($status !== 'running') {
return response()->json([
'message' => 'Application is not running.',
], 400);
}
$commands = collect([
executeInDocker($container['Names'], $request->command),
]);
$res = instant_remote_process(command: $commands, server: $application->destination->server);
return response()->json([
'message' => 'Command executed.',
'response' => $res,
]);
}
private function validateDataApplications(Request $request, Server $server)
{
$teamId = getTeamIdFromToken();

View File

@@ -1541,16 +1541,10 @@ class DatabasesController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1595,10 +1589,14 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
DeleteResourceJob::dispatch(
resource: $database,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Database deletion request queued.',

View File

@@ -86,7 +86,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
@@ -138,7 +138,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);

View File

@@ -308,7 +308,7 @@ class ServersController extends Controller
$projects = Project::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;

View File

@@ -432,6 +432,10 @@ class ServicesController extends Controller
tags: ['Services'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -476,7 +480,14 @@ class ServicesController extends Controller
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
DeleteResourceJob::dispatch($service);
DeleteResourceJob::dispatch(
resource: $service,
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Service deletion request queued.',
@@ -516,7 +527,8 @@ class ServicesController extends Controller
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -619,7 +631,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -738,7 +751,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -853,7 +867,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -953,7 +968,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1025,9 +1041,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
])
]
)
),
]
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1101,9 +1119,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
])
]
)
),
]
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1177,9 +1197,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
])
]
)
),
]
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',

View File

@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -22,7 +21,7 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (! $user) {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403, 'Registration is disabled');
}

View File

@@ -14,7 +14,7 @@ class ApiAllowed
if (isCloud()) {
return $next($request);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}

View File

@@ -12,7 +12,6 @@ use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@@ -27,6 +26,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Sleep;
use Illuminate\Support\Str;
use RuntimeException;
@@ -210,7 +210,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
ray('New container name: ', $this->container_name)->green();
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
// Set preview fqdn
@@ -514,7 +513,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
'hidden' => true,
'ignore_errors' => true,
], [
"docker network connect {$networkId} coolify-proxy || true",
"docker network connect {$networkId} coolify-proxy >/dev/null 2>&1 || true",
'hidden' => true,
'ignore_errors' => true,
]);
@@ -919,10 +918,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
}
}
@@ -962,7 +961,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
@@ -970,7 +969,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}");
@@ -978,10 +977,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
$envs->push("COOLIFY_BRANCH=\"{$local_branch}\"");
}
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
$envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\"");
}
}
@@ -1334,7 +1333,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function prepare_builder_image()
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$helperImage = "{$helperImage}:{$settings->helper_version}";
// Get user home directory
@@ -1456,10 +1455,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
executeInDocker($this->deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'),
],
[
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
executeInDocker($this->deployment_uuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}"),
'hidden' => true,
'save' => 'git_commit_sha',
],
]
);
} else {
$this->execute_remote_command(
@@ -2049,6 +2048,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2068,6 +2071,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2110,6 +2117,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2129,6 +2140,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2157,6 +2172,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2176,6 +2195,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
@@ -2187,20 +2210,40 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
}
/**
* @param int $timeout in seconds
*/
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
private function graceful_shutdown_container(string $containerName, int $timeout = 300)
{
try {
$process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
$startTime = time();
while ($process->running()) {
if (time() - $startTime >= $timeout) {
$this->execute_remote_command(
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
);
} catch (\Exception $error) {
// report error if needed
break;
}
usleep(100000);
}
$isRunning = $this->execute_remote_command(
["docker inspect -f '{{.State.Running}}' $containerName", 'hidden' => true, 'ignore_errors' => true]
) === 'true';
if ($isRunning) {
$this->execute_remote_command(
["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true]
);
}
} catch (\Exception $error) {
$this->application_deployment_queue->addLogEntry("Error stopping container $containerName: ".$error->getMessage(), 'stderr');
}
$this->remove_container($containerName);
}
private function remove_container(string $containerName)
{
$this->execute_remote_command(
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
);

View File

@@ -2,15 +2,14 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -22,7 +21,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$settings = instanceSettings();
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();

View File

@@ -21,11 +21,10 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
{
try {
ray('Cleaning up helper containers on '.$this->server->name);
$containers = instant_remote_process(['docker container ps --filter "ancestor=ghcr.io/coollabsio/coolify-helper:next" --filter "ancestor=ghcr.io/coollabsio/coolify-helper:latest" --format \'{{json .}}\''], $this->server, false);
$containers = format_docker_command_output_to_json($containers);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerId = data_get($container, 'ID');
$containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false);
$containerIds = collect(json_decode($containers))->pluck('ID');
if ($containerIds->count() > 0) {
foreach ($containerIds as $containerId) {
ray('Removing container '.$containerId);
instant_remote_process(['docker container rm -f '.$containerId], $this->server, false);
}

View File

@@ -3,12 +3,14 @@
namespace App\Jobs;
use App\Models\Server;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
class CleanupStaleMultiplexedConnections implements ShouldQueue
{
@@ -16,22 +18,65 @@ class CleanupStaleMultiplexedConnections implements ShouldQueue
public function handle()
{
Server::chunk(100, function ($servers) {
foreach ($servers as $server) {
$this->cleanupStaleConnection($server);
}
});
$this->cleanupStaleConnections();
$this->cleanupNonExistentServerConnections();
}
private function cleanupStaleConnection(Server $server)
private function cleanupStaleConnections()
{
$muxSocket = "/tmp/mux_{$server->id}";
$checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
$muxFiles = Storage::disk('ssh-mux')->files();
foreach ($muxFiles as $muxFile) {
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
$server = Server::where('uuid', $serverUuid)->first();
if (! $server) {
$this->removeMultiplexFile($muxFile);
continue;
}
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$muxFile}";
$checkCommand = "ssh -O check -o ControlPath={$muxSocket} {$server->user}@{$server->ip} 2>/dev/null";
$checkProcess = Process::run($checkCommand);
if ($checkProcess->exitCode() !== 0) {
$closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null";
$this->removeMultiplexFile($muxFile);
} else {
$muxContent = Storage::disk('ssh-mux')->get($muxFile);
$establishedAt = Carbon::parse(substr($muxContent, 37));
$expirationTime = $establishedAt->addSeconds(config('constants.ssh.mux_persist_time'));
if (Carbon::now()->isAfter($expirationTime)) {
$this->removeMultiplexFile($muxFile);
}
}
}
}
private function cleanupNonExistentServerConnections()
{
$muxFiles = Storage::disk('ssh-mux')->files();
$existingServerUuids = Server::pluck('uuid')->toArray();
foreach ($muxFiles as $muxFile) {
$serverUuid = $this->extractServerUuidFromMuxFile($muxFile);
if (! in_array($serverUuid, $existingServerUuids)) {
$this->removeMultiplexFile($muxFile);
}
}
}
private function extractServerUuidFromMuxFile($muxFile)
{
return substr($muxFile, 4);
}
private function removeMultiplexFile($muxFile)
{
$muxSocket = "/var/www/html/storage/app/ssh/mux/{$muxFile}";
$closeCommand = "ssh -O exit -o ControlPath={$muxSocket} localhost 2>/dev/null";
Process::run($closeCommand);
}
Storage::disk('ssh-mux')->delete($muxFile);
}
}

View File

@@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
@@ -25,16 +24,6 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): int
{
return $this->server->uuid;
}
public function handle()
{
GetContainersStatus::run($this->server);

View File

@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
@@ -22,10 +21,8 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -64,10 +61,12 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
public function handle(): void
{
try {
$this->team = Team::findOrFail($this->backup->team_id);
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
@@ -77,28 +76,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
if (is_null($this->server)) {
throw new \Exception('Server not found?!');
}
public function middleware(): array
{
return [new WithoutOverlapping($this->backup->id)];
}
public function uniqueId(): int
{
return $this->backup->id;
}
public function handle(): void
{
try {
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
StopDatabase::run($this->database);
$this->database->delete();
return;
if (is_null($this->database)) {
throw new \Exception('Database not found?!');
}
BackupCreated::dispatch($this->team->id);
@@ -249,7 +231,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
@@ -337,9 +318,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
if ($this->team) {
BackupCreated::dispatch($this->team->id);
}
}
}
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
@@ -481,7 +464,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function upload_to_s3(): void
{
try {
ray($this->backup_location);
if (is_null($this->s3)) {
return;
}
@@ -491,20 +473,81 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint;
$this->s3->testConnection(shouldSave: true);
$configName = new Cuid2;
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$network = $this->database->service->destination->network;
} else {
$network = $this->database->destination->network;
}
$s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
$commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
$commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
$this->ensureHelperImageAvailable();
$fullImageName = $this->getFullImageName();
if (isDev()) {
if ($this->database->name === 'coolify-db') {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
}
} else {
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
}
if ($this->s3->isHetzner()) {
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
} else {
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
}
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
throw $e;
} finally {
$removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
$removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
instant_remote_process($removeConfigCommands, $this->server, false);
$command = "docker rm -f backup-of-{$this->backup->uuid}";
instant_remote_process([$command], $this->server);
}
}
private function ensureHelperImageAvailable(): void
{
$fullImageName = $this->getFullImageName();
$imageExists = $this->checkImageExists($fullImageName);
if (! $imageExists) {
$this->pullHelperImage($fullImageName);
}
}
private function checkImageExists(string $fullImageName): bool
{
$result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false);
return trim($result) === 'exists';
}
private function pullHelperImage(string $fullImageName): void
{
try {
instant_remote_process(["docker pull {$fullImageName}"], $this->server);
} catch (\Exception $e) {
$errorMessage = 'Failed to pull helper image: '.$e->getMessage();
$this->add_to_backup_output($errorMessage);
throw new \RuntimeException($errorMessage);
}
}
private function getFullImageName(): string
{
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$latestVersion = $settings->helper_version;
return "{$helperImage}:{$latestVersion}";
}
}

View File

@@ -1,62 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Team;
use App\Notifications\Database\DailyBackup;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public function __construct() {}
public function handle()
{
// $teams = Team::all();
// foreach ($teams as $team) {
// $scheduled_backups = $team->scheduledDatabaseBackups()->get();
// if ($scheduled_backups->isEmpty()) {
// continue;
// }
// foreach ($scheduled_backups as $scheduled_backup) {
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
// if ($last_days_backups->isEmpty()) {
// continue;
// }
// $failed = $last_days_backups->where('status', 'failed');
// }
// }
// $scheduled_backups = ScheduledDatabaseBackup::all();
// $databases = collect();
// $teams = collect();
// foreach ($scheduled_backups as $scheduled_backup) {
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
// if ($last_days_backups->isEmpty()) {
// continue;
// }
// $failed = $last_days_backups->where('status', 'failed');
// $database = $scheduled_backup->database;
// $team = $database->team();
// $teams->put($team->id, $team);
// $databases->put("{$team->id}:{$database->name}", [
// 'failed_count' => $failed->count(),
// ]);
// }
// foreach ($databases as $name => $database) {
// [$team_id, $name] = explode(':', $name);
// $team = $teams->get($team_id);
// $team?->notify(new DailyBackup($databases));
// }
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Jobs;
use App\Actions\Application\StopApplication;
use App\Actions\Database\StopDatabase;
use App\Actions\Server\CleanupDocker;
use App\Actions\Service\DeleteService;
use App\Actions\Service\StopService;
use App\Models\Application;
@@ -30,8 +31,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
public bool $deleteConfigurations = false,
public bool $deleteVolumes = false) {}
public bool $deleteConfigurations = true,
public bool $deleteVolumes = true,
public bool $dockerCleanup = true,
public bool $deleteConnectedNetworks = true
) {}
public function handle()
{
@@ -51,11 +55,11 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
case 'standalone-dragonfly':
case 'standalone-clickhouse':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopDatabase::run($this->resource);
StopDatabase::run($this->resource, true);
break;
case 'service':
StopService::run($this->resource);
DeleteService::run($this->resource);
StopService::run($this->resource, true);
DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks);
break;
}
@@ -65,12 +69,31 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
if ($this->deleteConfigurations) {
$this->resource?->delete_configurations();
}
$isDatabase = $this->resource instanceof StandalonePostgresql
|| $this->resource instanceof StandaloneRedis
|| $this->resource instanceof StandaloneMongodb
|| $this->resource instanceof StandaloneMysql
|| $this->resource instanceof StandaloneMariadb
|| $this->resource instanceof StandaloneKeydb
|| $this->resource instanceof StandaloneDragonfly
|| $this->resource instanceof StandaloneClickhouse;
$server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server');
if (($this->dockerCleanup || $isDatabase) && $server) {
CleanupDocker::dispatch($server, true);
}
if ($this->deleteConnectedNetworks && ! $isDatabase) {
$this->resource?->delete_connected_networks($this->resource->uuid);
}
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
throw $e;
} finally {
$this->resource->forceDelete();
if ($this->dockerCleanup) {
CleanupDocker::dispatch($server, true);
}
Artisan::queue('cleanup:stucked-resources');
}
}

View File

@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
@@ -24,17 +23,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public ?string $usageBefore = null;
public function __construct(public Server $server) {}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->id)];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function __construct(public Server $server, public bool $manualCleanup = false) {}
public function handle(): void
{
@@ -42,8 +31,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
if (! $this->server->isFunctional()) {
return;
}
if ($this->server->settings->force_docker_cleanup) {
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) {
Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name);
CleanupDocker::run(server: $this->server);
return;

View File

@@ -8,7 +8,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
@@ -25,16 +24,6 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(public GithubApp $github_app) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->github_app->uuid))];
}
public function uniqueId(): int
{
return $this->github_app->uuid;
}
public function handle()
{
try {

View File

@@ -2,14 +2,12 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
@@ -19,17 +17,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 1000;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server) {}
public function __construct() {}
public function handle(): void
{
@@ -37,13 +25,13 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$latest_version = data_get($versions, 'coolify.helper.version');
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {
// New version available
$helperImage = config('coolify.helper_image');
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
// $helperImage = config('coolify.helper_image');
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
$settings->update(['helper_version' => $latest_version]);
}
}

View File

@@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
@@ -18,16 +17,6 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 1000;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server) {}
public function handle(): void

View File

@@ -13,7 +13,6 @@ use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ScheduledTaskJob implements ShouldQueue
@@ -56,24 +55,17 @@ class ScheduledTaskJob implements ShouldQueue
{
if ($this->resource instanceof Application) {
$timezone = $this->resource->destination->server->settings->server_timezone;
return $timezone;
} elseif ($this->resource instanceof Service) {
$timezone = $this->resource->server->settings->server_timezone;
return $timezone;
}
return 'UTC';
}
public function middleware(): array
{
return [new WithoutOverlapping($this->task->id)];
}
public function uniqueId(): int
{
return $this->task->id;
}
public function handle(): void
{

View File

@@ -16,7 +16,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
@@ -24,7 +23,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $tries = 1;
public $timeout = 60;
@@ -45,16 +44,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function handle()
{
try {
@@ -80,8 +69,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
return 'No containers found.';
}
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
if ($this->server->isLogDrainEnabled()) {
$this->checkLogDrainContainer();
}
}
} catch (\Throwable $e) {
ray($e->getMessage());
@@ -93,7 +84,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
private function serverStatus()
{
['uptime' => $uptime] = $this->server->validateConnection();
['uptime' => $uptime] = $this->server->validateConnection(false);
if ($uptime) {
if ($this->server->unreachable_notification_sent === true) {
$this->server->update(['unreachable_notification_sent' => false]);
@@ -126,9 +117,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
private function checkLogDrainContainer()
{
if (! $this->server->isLogDrainEnabled()) {
return;
}
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-log-drain';
})->first();

View File

@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
@@ -26,16 +25,6 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(public Team $team) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->team->uuid))];
}
public function uniqueId(): int
{
return $this->team->uuid;
}
public function handle()
{
try {

View File

@@ -8,7 +8,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
@@ -26,16 +25,6 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): int
{
return $this->server->uuid;
}
public function handle()
{
if (! $this->server->isServerReady($this->tries)) {

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 60;
public $containers;
public $applications;
public $databases;
public $services;
public $previews;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server) {}
public function handle()
{
try {
if (! $this->server->isFunctional()) {
ray('Server is not ready.');
return 'Server is not ready.';
}
$team = $this->server->team;
$percentage = $this->server->storageCheck();
if ($percentage > 1) {
ray('Server storage is at '.$percentage.'%');
}
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -23,7 +22,7 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
{
try {
CheckForUpdatesJob::dispatchSync();
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->new_version_available) {
Log::info('No new version available. Skipping update.');

View File

@@ -141,7 +141,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
if (! $this->createdServer) {
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
}
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
return $this->validateServer('localhost');
} elseif ($this->selectedServerType === 'remote') {
@@ -175,7 +175,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
return;
}
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
$this->serverPublicKey = $this->createdServer->privateKey->getPublicKey();
$this->updateServerDetails();
$this->currentState = 'validate-server';
}
@@ -231,17 +231,24 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function savePrivateKey()
{
$this->validate([
'privateKeyName' => 'required',
'privateKey' => 'required',
'privateKeyName' => 'required|string|max:255',
'privateKeyDescription' => 'nullable|string|max:255',
'privateKey' => 'required|string',
]);
$this->createdPrivateKey = PrivateKey::create([
try {
$privateKey = PrivateKey::createAndStore([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id,
]);
$this->createdPrivateKey->save();
$this->createdPrivateKey = $privateKey;
$this->currentState = 'create-server';
} catch (\Exception $e) {
$this->addError('privateKey', 'Failed to save private key: '.$e->getMessage());
}
}
public function saveServer()

View File

@@ -1,21 +0,0 @@
<?php
namespace App\Livewire\CommandCenter;
use App\Models\Server;
use Livewire\Component;
class Index extends Component
{
public $servers = [];
public function mount()
{
$this->servers = Server::isReachable()->get();
}
public function render()
{
return view('livewire.command-center.index');
}
}

View File

@@ -30,8 +30,7 @@ class Dashboard extends Component
public function cleanup_queue()
{
$this->dispatch('success', 'Cleanup started.');
Artisan::queue('cleanup:application-deployment-queue', [
Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}

View File

@@ -38,7 +38,7 @@ class Form extends Component
}
$this->destination->delete();
return redirect()->route('dashboard');
return redirect()->route('destination.all');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -47,7 +47,7 @@ class Help extends Component
]
);
$mail->subject("[HELP]: {$this->subject}");
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
$url = 'https://app.coolify.io/api/feedback';

View File

@@ -2,13 +2,28 @@
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class NavbarDeleteTeam extends Component
{
public function delete()
public $team;
public function mount()
{
$this->team = currentTeam()->name;
}
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
$currentTeam = currentTeam();
$currentTeam->delete();

View File

@@ -172,7 +172,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->smtp_enabled) {
$team = currentTeam();
$team->update([

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application\Deployment;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Support\Collection;
use Livewire\Component;
class Show extends Component

View File

@@ -21,6 +21,8 @@ class Heading extends Component
protected string $deploymentUuid;
public bool $docker_cleanup = true;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
@@ -102,7 +104,7 @@ class Heading extends Component
public function stop()
{
StopApplication::run($this->application);
StopApplication::run($this->application, false, $this->docker_cleanup);
$this->application->status = 'exited';
$this->application->save();
if ($this->application->additional_servers->count() > 0) {
@@ -135,4 +137,13 @@ class Heading extends Component
'environment_name' => $this->parameters['environment_name'],
]);
}
public function render()
{
return view('livewire.project.application.heading', [
'checkboxes' => [
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
],
]);
}
}

View File

@@ -5,7 +5,9 @@ namespace App\Livewire\Project\Application;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Process\InvokedProcess;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Process;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
@@ -184,17 +186,20 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
try {
$server = $this->application->destination->server;
$timeout = 300;
if ($this->application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server);
} else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
$containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
$this->stopContainers($containers, $server, $timeout);
}
}
GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high');
$this->dispatch('reloadWindow');
GetContainersStatus::run($server);
$this->application->refresh();
$this->dispatch('containerStatusUpdated');
$this->dispatch('success', 'Preview Deployment stopped.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -203,16 +208,21 @@ class Previews extends Component
public function delete(int $pull_request_id)
{
try {
$server = $this->application->destination->server;
$timeout = 300;
if ($this->application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server);
} else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
$containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray();
$this->stopContainers($containers, $server, $timeout);
}
}
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
ApplicationPreview::where('application_id', $this->application->id)
->where('pull_request_id', $pull_request_id)
->first()
->delete();
$this->application->refresh();
$this->dispatch('update_links');
$this->dispatch('success', 'Preview deleted.');
@@ -220,4 +230,49 @@ class Previews extends Component
return handleError($e, $this);
}
}
private function stopContainers(array $containers, $server, int $timeout)
{
$processes = [];
foreach ($containers as $container) {
$containerName = str_replace('/', '', $container['Names']);
$processes[$containerName] = $this->stopContainer($containerName, $timeout);
}
$startTime = time();
while (count($processes) > 0) {
$finishedProcesses = array_filter($processes, function ($process) {
return ! $process->running();
});
foreach (array_keys($finishedProcesses) as $containerName) {
unset($processes[$containerName]);
$this->removeContainer($containerName, $server);
}
if (time() - $startTime >= $timeout) {
$this->forceStopRemainingContainers(array_keys($processes), $server);
break;
}
usleep(100000);
}
}
private function stopContainer(string $containerName, int $timeout): InvokedProcess
{
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
}
private function removeContainer(string $containerName, $server)
{
instant_remote_process(["docker rm -f $containerName"], $server, throwError: false);
}
private function forceStopRemainingContainers(array $containerNames, $server)
{
foreach ($containerNames as $containerName) {
instant_remote_process(["docker kill $containerName"], $server, throwError: false);
$this->removeContainer($containerName, $server);
}
}
}

View File

@@ -3,6 +3,8 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Spatie\Url\Url;
@@ -12,6 +14,12 @@ class BackupEdit extends Component
public $s3s;
public bool $delete_associated_backups_locally = false;
public bool $delete_associated_backups_s3 = false;
public bool $delete_associated_backups_sftp = false;
public ?string $status = null;
public array $parameters;
@@ -46,10 +54,24 @@ class BackupEdit extends Component
}
}
public function delete()
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
try {
if ($this->delete_associated_backups_locally) {
$this->deleteAssociatedBackupsLocally();
}
if ($this->delete_associated_backups_s3) {
$this->deleteAssociatedBackupsS3();
}
$this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous();
$url = Url::fromString($previousUrl);
@@ -104,4 +126,66 @@ class BackupEdit extends Component
$this->dispatch('error', $e->getMessage());
}
}
public function deleteAssociatedBackupsLocally()
{
$executions = $this->backup->executions;
$backupFolder = null;
foreach ($executions as $execution) {
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$server = $this->backup->database->service->destination->server;
} else {
$server = $this->backup->database->destination->server;
}
if (! $backupFolder) {
$backupFolder = dirname($execution->filename);
}
delete_backup_locally($execution->filename, $server);
$execution->delete();
}
if ($backupFolder) {
$this->deleteEmptyBackupFolder($backupFolder, $server);
}
}
public function deleteAssociatedBackupsS3()
{
//Add function to delete backups from S3
}
public function deleteAssociatedBackupsSftp()
{
//Add function to delete backups from SFTP
}
private function deleteEmptyBackupFolder($folderPath, $server)
{
$checkEmpty = instant_remote_process(["[ -z \"$(ls -A '$folderPath')\" ] && echo 'empty' || echo 'not empty'"], $server);
if (trim($checkEmpty) === 'empty') {
instant_remote_process(["rmdir '$folderPath'"], $server);
$parentFolder = dirname($folderPath);
$checkParentEmpty = instant_remote_process(["[ -z \"$(ls -A '$parentFolder')\" ] && echo 'empty' || echo 'not empty'"], $server);
if (trim($checkParentEmpty) === 'empty') {
instant_remote_process(["rmdir '$parentFolder'"], $server);
}
}
}
public function render()
{
return view('livewire.project.database.backup-edit', [
'checkboxes' => [
['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')],
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
],
]);
}
}

View File

@@ -3,18 +3,28 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Attributes\On;
use Livewire\Component;
class BackupExecutions extends Component
{
public ?ScheduledDatabaseBackup $backup = null;
public $database;
public $executions = [];
public $setDeletableBackup;
public $delete_backup_s3 = true;
public $delete_backup_sftp = true;
public function getListeners()
{
$userId = auth()->user()->id;
$userId = Auth::id();
return [
"echo-private:team.{$userId},BackupCreated" => 'refreshBackupExecutions',
@@ -31,19 +41,36 @@ class BackupExecutions extends Component
}
}
public function deleteBackup($exeuctionId)
#[On('deleteBackup')]
public function deleteBackup($executionId, $password)
{
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
$execution = $this->backup->executions()->where('id', $executionId)->first();
if (is_null($execution)) {
$this->dispatch('error', 'Backup execution not found.');
return;
}
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server);
} else {
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
}
if ($this->delete_backup_s3) {
// Add logic to delete from S3
}
if ($this->delete_backup_sftp) {
// Add logic to delete from SFTP
}
$execution->delete();
$this->dispatch('success', 'Backup deleted.');
$this->refreshBackupExecutions();
@@ -82,6 +109,7 @@ class BackupExecutions extends Component
return $server;
}
}
return null;
}
@@ -92,6 +120,7 @@ class BackupExecutions extends Component
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
}
@@ -104,6 +133,17 @@ class BackupExecutions extends Component
} catch (\Exception $e) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}
return $dateObj->format('Y-m-d H:i:s T');
}
public function render()
{
return view('livewire.project.database.backup-executions', [
'checkboxes' => [
['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'],
['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
],
]);
}
}

View File

@@ -14,6 +14,8 @@ class Heading extends Component
public array $parameters;
public $docker_cleanup = true;
public function getListeners()
{
$userId = auth()->user()->id;
@@ -54,7 +56,7 @@ class Heading extends Component
public function stop()
{
StopDatabase::run($this->database);
StopDatabase::run($this->database, false, $this->docker_cleanup);
$this->database->status = 'exited';
$this->database->save();
$this->check_status();
@@ -71,4 +73,13 @@ class Heading extends Component
$activity = StartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id);
}
public function render()
{
return view('livewire.project.database.heading', [
'checkboxes' => [
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
],
]);
}
}

View File

@@ -13,9 +13,12 @@ class DeleteEnvironment extends Component
public bool $disabled = false;
public string $environmentName = '';
public function mount()
{
$this->parameters = get_route_parameters();
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
}
public function delete()

View File

@@ -13,9 +13,12 @@ class DeleteProject extends Component
public bool $disabled = false;
public string $projectName = '';
public function mount()
{
$this->parameters = get_route_parameters();
$this->projectName = Project::findOrFail($this->project_id)->name;
}
public function delete()

View File

@@ -52,7 +52,7 @@ class Configuration extends Component
$application = $this->service->applications->find($id);
if ($application) {
$application->restart();
$this->dispatch('success', 'Application restarted successfully.');
$this->dispatch('success', 'Service application restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
@@ -65,7 +65,7 @@ class Configuration extends Component
$database = $this->service->databases->find($id);
if ($database) {
$database->restart();
$this->dispatch('success', 'Database restarted successfully.');
$this->dispatch('success', 'Service database restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);

View File

@@ -14,6 +14,8 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class FileStorage extends Component
@@ -83,8 +85,14 @@ class FileStorage extends Component
}
}
public function delete()
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
try {
$message = 'File deleted.';
if ($this->fileStorage->is_directory) {
@@ -129,6 +137,13 @@ class FileStorage extends Component
public function render()
{
return view('livewire.project.service.file-storage');
return view('livewire.project.service.file-storage', [
'directoryDeletionCheckboxes' => [
['id' => 'permanently_delete', 'label' => 'The selected directory and all its contents will be permantely deleted form the server.'],
],
'fileDeletionCheckboxes' => [
['id' => 'permanently_delete', 'label' => 'The selected file will be permanently deleted form the server.'],
],
]);
}
}

View File

@@ -20,6 +20,10 @@ class Navbar extends Component
public $isDeploymentProgress = false;
public $docker_cleanup = true;
public $title = 'Configuration';
public function mount()
{
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
@@ -40,7 +44,7 @@ class Navbar extends Component
public function serviceStarted()
{
$this->dispatch('success', 'Service status changed.');
// $this->dispatch('success', 'Service status changed.');
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
$this->service->isConfigurationChanged(true);
$this->dispatch('configurationChanged');
@@ -60,11 +64,6 @@ class Navbar extends Component
$this->dispatch('success', 'Service status updated.');
}
public function render()
{
return view('livewire.project.service.navbar');
}
public function checkDeployments()
{
try {
@@ -95,14 +94,9 @@ class Navbar extends Component
$this->dispatch('activityMonitor', $activity->id);
}
public function stop(bool $forceCleanup = false)
public function stop()
{
StopService::run($this->service);
if ($forceCleanup) {
$this->dispatch('success', 'Containers cleaned up.');
} else {
$this->dispatch('success', 'Service stopped.');
}
StopService::run($this->service, false, $this->docker_cleanup);
ServiceStatusChanged::dispatch();
}
@@ -115,10 +109,19 @@ class Navbar extends Component
return;
}
PullImage::run($this->service);
StopService::run($this->service);
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}
public function render()
{
return view('livewire.project.service.navbar', [
'checkboxes' => [
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
],
]);
}
}

View File

@@ -3,6 +3,8 @@
namespace App\Livewire\Project\Service;
use App\Models\ServiceApplication;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class ServiceApplicationView extends Component
@@ -11,6 +13,10 @@ class ServiceApplicationView extends Component
public $parameters;
public $docker_cleanup = true;
public $delete_volumes = true;
protected $rules = [
'application.human_name' => 'nullable',
'application.description' => 'nullable',
@@ -23,11 +29,6 @@ class ServiceApplicationView extends Component
'application.is_stripprefix_enabled' => 'nullable|boolean',
];
public function render()
{
return view('livewire.project.service.service-application-view');
}
public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
@@ -56,8 +57,14 @@ class ServiceApplicationView extends Component
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
}
public function delete()
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
try {
$this->application->delete();
$this->dispatch('success', 'Application deleted.');
@@ -91,4 +98,17 @@ class ServiceApplicationView extends Component
$this->dispatch('generateDockerCompose');
}
}
public function render()
{
return view('livewire.project.service.service-application-view', [
'checkboxes' => [
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
// ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
],
]);
}
}

View File

@@ -33,7 +33,7 @@ class StackForm extends Component
$key = data_get($field, 'key');
$value = data_get($field, 'value');
$rules = data_get($field, 'rules', 'nullable');
$isPassword = data_get($field, 'isPassword');
$isPassword = data_get($field, 'isPassword', false);
$this->fields->put($key, [
'serviceName' => $serviceName,
'key' => $key,
@@ -47,7 +47,15 @@ class StackForm extends Component
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortBy('name');
$this->fields = $this->fields->groupBy('serviceName')->map(function ($group) {
return $group->sortBy(function ($field) {
return data_get($field, 'isPassword') ? 1 : 0;
})->mapWithKeys(function ($field) {
return [$field['key'] => $field];
});
})->flatMap(function ($group) {
return $group;
});
}
public function saveCompose($raw)

View File

@@ -3,6 +3,11 @@
namespace App\Livewire\Project\Shared;
use App\Jobs\DeleteResourceJob;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -10,6 +15,8 @@ class Danger extends Component
{
public $resource;
public $resourceName;
public $projectUuid;
public $environmentName;
@@ -18,22 +25,95 @@ class Danger extends Component
public bool $delete_volumes = true;
public bool $docker_cleanup = true;
public bool $delete_connected_networks = true;
public ?string $modalId = null;
public string $resourceDomain = '';
public function mount()
{
$this->modalId = new Cuid2;
$parameters = get_route_parameters();
$this->modalId = new Cuid2;
$this->projectUuid = data_get($parameters, 'project_uuid');
$this->environmentName = data_get($parameters, 'environment_name');
if ($this->resource === null) {
if (isset($parameters['service_uuid'])) {
$this->resource = Service::where('uuid', $parameters['service_uuid'])->first();
} elseif (isset($parameters['stack_service_uuid'])) {
$this->resource = ServiceApplication::where('uuid', $parameters['stack_service_uuid'])->first()
?? ServiceDatabase::where('uuid', $parameters['stack_service_uuid'])->first();
}
}
public function delete()
if ($this->resource === null) {
$this->resourceName = 'Unknown Resource';
return;
}
if (! method_exists($this->resource, 'type')) {
$this->resourceName = 'Unknown Resource';
return;
}
switch ($this->resource->type()) {
case 'application':
$this->resourceName = $this->resource->name ?? 'Application';
break;
case 'standalone-postgresql':
case 'standalone-redis':
case 'standalone-mongodb':
case 'standalone-mysql':
case 'standalone-mariadb':
case 'standalone-keydb':
case 'standalone-dragonfly':
case 'standalone-clickhouse':
$this->resourceName = $this->resource->name ?? 'Database';
break;
case 'service':
$this->resourceName = $this->resource->name ?? 'Service';
break;
case 'service-application':
$this->resourceName = $this->resource->name ?? 'Service Application';
break;
case 'service-database':
$this->resourceName = $this->resource->name ?? 'Service Database';
break;
default:
$this->resourceName = 'Unknown Resource';
}
}
public function delete($password)
{
if (isProduction()) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
}
if (! $this->resource) {
$this->addError('resource', 'Resource not found.');
return;
}
try {
// $this->authorize('delete', $this->resource);
$this->resource->delete();
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
DeleteResourceJob::dispatch(
$this->resource,
$this->delete_configurations,
$this->delete_volumes,
$this->docker_cleanup,
$this->delete_connected_networks
);
return redirect()->route('project.resource.index', [
'project_uuid' => $this->projectUuid,
@@ -43,4 +123,19 @@ class Danger extends Component
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.shared.danger', [
'checkboxes' => [
['id' => 'delete_volumes', 'label' => __('resource.delete_volumes')],
['id' => 'delete_connected_networks', 'label' => __('resource.delete_connected_networks')],
['id' => 'delete_configurations', 'label' => __('resource.delete_configurations')],
['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')],
// ['id' => 'delete_associated_backups_locally', 'label' => 'All backups associated with this Ressource will be permanently deleted from local storage.'],
// ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected S3 Storage.'],
// ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this Ressource will be permanently deleted from the selected SFTP Storage.']
],
]);
}
}

View File

@@ -8,6 +8,8 @@ use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -115,8 +117,14 @@ class Destination extends Component
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
}
public function removeServer(int $network_id, int $server_id)
public function removeServer(int $network_id, int $server_id, $password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');

View File

@@ -2,18 +2,18 @@
namespace App\Livewire\Project\Shared;
use App\Actions\Server\RunCommand;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
class ExecuteContainerCommand extends Component
{
public string $command;
public $selected_container = 'default';
public string $container;
public $container;
public Collection $containers;
@@ -23,8 +23,6 @@ class ExecuteContainerCommand extends Component
public string $type;
public string $workDir = '';
public Server $server;
public Collection $servers;
@@ -33,11 +31,13 @@ class ExecuteContainerCommand extends Component
'server' => 'required',
'container' => 'required',
'command' => 'required',
'workDir' => 'nullable',
];
public function mount()
{
if (! auth()->user()->isAdmin()) {
abort(403);
}
$this->parameters = get_route_parameters();
$this->containers = collect();
$this->servers = collect();
@@ -62,24 +62,13 @@ class ExecuteContainerCommand extends Component
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
$this->container = $this->resource->uuid;
$this->containers->push($this->container);
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
$this->containers->push(data_get($application, 'name').'-'.data_get($this->resource, 'uuid'));
});
$this->resource->databases()->get()->each(function ($database) {
$this->containers->push(data_get($database, 'name').'-'.data_get($this->resource, 'uuid'));
});
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
}
if ($this->containers->count() > 0) {
$this->container = $this->containers->first();
}
}
public function loadContainers()
@@ -96,6 +85,8 @@ class ExecuteContainerCommand extends Component
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
foreach ($containers as $container) {
// if container state is running
if (data_get($container, 'State') === 'running') {
$payload = [
'server' => $server,
'container' => $container,
@@ -103,43 +94,69 @@ class ExecuteContainerCommand extends Component
$this->containers = $this->containers->push($payload);
}
}
} elseif (data_get($this->parameters, 'database_uuid')) {
if ($this->resource->isRunning()) {
$this->containers = $this->containers->push([
'server' => $server,
'container' => [
'Names' => $this->resource->uuid,
],
]);
}
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->resource->applications()->get()->each(function ($application) {
if ($application->isRunning()) {
$this->containers->push([
'server' => $this->resource->server,
'container' => [
'Names' => data_get($application, 'name').'-'.data_get($this->resource, 'uuid'),
],
]);
}
});
$this->resource->databases()->get()->each(function ($database) {
if ($database->isRunning()) {
$this->containers->push([
'server' => $this->resource->server,
'container' => [
'Names' => data_get($database, 'name').'-'.data_get($this->resource, 'uuid'),
],
]);
}
});
}
}
if ($this->containers->count() > 0) {
if (data_get($this->parameters, 'application_uuid')) {
$this->container = data_get($this->containers->first(), 'container.Names');
} elseif (data_get($this->parameters, 'database_uuid')) {
$this->container = $this->containers->first();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->container = $this->containers->first();
}
}
}
public function runCommand()
#[On('connectToContainer')]
public function connectToContainer()
{
if ($this->selected_container === 'default') {
$this->dispatch('error', 'Please select a container.');
return;
}
try {
if (data_get($this->parameters, 'application_uuid')) {
$container = $this->containers->where('container.Names', $this->container)->first();
$container_name = data_get($container, 'container.Names');
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($container, 'server');
} else {
$container_name = $this->container;
$server = $this->servers->first();
}
$server = data_get($this->container, 'server');
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; ".str_replace("'", "'\''", $this->command)."'";
if (! empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
} else {
$exec = "docker exec {$container_name} {$cmd}";
}
$activity = RunCommand::run(server: $server, command: $exec);
$this->dispatch('activityMonitor', $activity->id);
$this->dispatch(
'send-terminal-command',
isset($container),
data_get($container, 'container.Names'),
data_get($container, 'server.uuid')
);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Shared;
use App\Helpers\SshMultiplexingHelper;
use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
@@ -108,14 +109,14 @@ class GetLogs extends Component
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
} else {
$command = "docker logs -n {$this->numberOfLines} -t {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
}
} else {
if ($this->server->isSwarm()) {
@@ -124,14 +125,14 @@ class GetLogs extends Component
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
} else {
$command = "docker logs -n {$this->numberOfLines} {$this->container}";
if ($this->server->isNonRoot()) {
$command = parseCommandsByLineForSudo(collect($command), $this->server);
$command = $command[0];
}
$sshCommand = generateSshCommand($this->server, $command);
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
}
}
if ($refresh) {

View File

@@ -7,7 +7,9 @@ use Livewire\Component;
class Executions extends Component
{
public $executions = [];
public $selectedKey;
public $task;
public function getListeners()
@@ -42,6 +44,7 @@ class Executions extends Component
return $this->task->service->destination->server;
}
}
return null;
}
@@ -52,6 +55,7 @@ class Executions extends Component
return 'UTC';
}
$serverTimezone = $server->settings->server_timezone;
return $serverTimezone;
}
@@ -64,6 +68,7 @@ class Executions extends Component
} catch (\Exception $e) {
$dateObj->setTimezone(new \DateTimeZone('UTC'));
}
return $dateObj->format('Y-m-d H:i:s T');
}
}

View File

@@ -20,6 +20,8 @@ class Show extends Component
public string $type;
public string $scheduledTaskName;
protected $rules = [
'task.enabled' => 'required|boolean',
'task.name' => 'required|string',
@@ -49,6 +51,7 @@ class Show extends Component
$this->modalId = new Cuid2;
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
$this->scheduledTaskName = $this->task->name;
}
public function instantSave()
@@ -75,9 +78,9 @@ class Show extends Component
$this->task->delete();
if ($this->type == 'application') {
return redirect()->route('project.application.configuration', $this->parameters);
return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName);
} else {
return redirect()->route('project.service.configuration', $this->parameters);
return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName);
}
} catch (\Exception $e) {
return handleError($e);

View File

@@ -3,6 +3,8 @@
namespace App\Livewire\Project\Shared\Storages;
use App\Models\LocalPersistentVolume;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class Show extends Component
@@ -36,8 +38,14 @@ class Show extends Component
$this->dispatch('success', 'Storage updated successfully');
}
public function delete()
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
}
$this->storage->delete();
$this->dispatch('refreshStorages');
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Livewire\Project\Shared;
use App\Helpers\SshMultiplexingHelper;
use App\Models\Server;
use Livewire\Attributes\On;
use Livewire\Component;
class Terminal extends Component
{
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'closeTerminal',
];
}
public function closeTerminal()
{
$this->dispatch('reloadWindow');
}
#[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
if ($isContainer) {
$status = getContainerStatus($server, $identifier);
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}
// ssh command is sent back to frontend then to websocket
// this is done because the websocket connection is not available here
// a better solution would be to remove websocket on NodeJS and work with something like
// 1. Laravel Pusher/Echo connection (not possible without a sdk)
// 2. Ratchet / Revolt / ReactPHP / Event Loop (possible but hard to implement and huge dependencies)
// 3. Just found out about this https://github.com/sirn-se/websocket-php, perhaps it can be used
// 4. Follow-up discussions here:
// - https://github.com/coollabsio/coolify/issues/2298
// - https://github.com/coollabsio/coolify/discussions/3362
$this->dispatch('send-back-command', $command);
}
public function render()
{
return view('livewire.project.shared.terminal');
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace App\Livewire;
use App\Actions\Server\RunCommand as ServerRunCommand;
use App\Models\Server;
use Livewire\Component;
class RunCommand extends Component
{
public string $command;
public $server;
public $servers = [];
protected $rules = [
'server' => 'required',
'command' => 'required',
];
protected $validationAttributes = [
'server' => 'server',
'command' => 'command',
];
public function mount($servers)
{
$this->servers = $servers;
$this->server = $servers[0]->uuid;
}
public function runCommand()
{
$this->validate();
try {
$activity = ServerRunCommand::run(server: Server::where('uuid', $this->server)->first(), command: $this->command);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Security;
use App\Models\InstanceSettings;
use Livewire\Component;
class ApiTokens extends Component
@@ -14,8 +15,12 @@ class ApiTokens extends Component
public bool $readOnly = true;
public bool $rootAccess = false;
public array $permissions = ['read-only'];
public $isApiEnabled;
public function render()
{
return view('livewire.security.api-tokens');
@@ -23,6 +28,7 @@ class ApiTokens extends Component
public function mount()
{
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
}
@@ -31,12 +37,11 @@ class ApiTokens extends Component
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
if (count($this->permissions) == 0) {
$this->permissions = ['*'];
}
$this->makeSureOneIsSelected();
}
public function updatedReadOnly()
@@ -44,11 +49,30 @@ class ApiTokens extends Component
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
if (count($this->permissions) == 0) {
$this->makeSureOneIsSelected();
}
public function updatedRootAccess()
{
if ($this->rootAccess) {
$this->permissions = ['*'];
$this->readOnly = false;
$this->viewSensitiveData = false;
} else {
$this->readOnly = true;
$this->permissions = ['read-only'];
}
}
public function makeSureOneIsSelected()
{
if (count($this->permissions) == 0) {
$this->permissions = ['read-only'];
$this->readOnly = true;
}
}
@@ -58,12 +82,6 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
// if ($this->viewSensitiveData) {
// $this->permissions[] = 'view:sensitive';
// }
// if ($this->readOnly) {
// $this->permissions[] = 'read-only';
// }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);

View File

@@ -3,17 +3,13 @@
namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component;
use phpseclib3\Crypt\PublicKeyLoader;
class Create extends Component
{
use WithRateLimiting;
public string $name = '';
public string $name;
public string $value;
public string $value = '';
public ?string $from = null;
@@ -26,72 +22,69 @@ class Create extends Component
'value' => 'required|string',
];
protected $validationAttributes = [
'name' => 'name',
'value' => 'private Key',
];
public function generateNewRSAKey()
{
try {
$this->rateLimit(10);
$this->name = generate_random_name();
$this->description = 'Created by Coolify';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->generateNewKey('rsa');
}
public function generateNewEDKey()
{
try {
$this->rateLimit(10);
$this->name = generate_random_name();
$this->description = 'Created by Coolify';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey('ed25519');
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->generateNewKey('ed25519');
}
public function updated($updateProperty)
private function generateNewKey($type)
{
if ($updateProperty === 'value') {
try {
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH', ['comment' => '']);
} catch (\Throwable $e) {
if ($this->$updateProperty === '') {
$this->publicKey = '';
} else {
$this->publicKey = 'Invalid private key';
$keyData = PrivateKey::generateNewKeyPair($type);
$this->setKeyData($keyData);
}
public function updated($property)
{
if ($property === 'value') {
$this->validatePrivateKey();
}
}
$this->validateOnly($updateProperty);
}
public function createPrivateKey()
{
$this->validate();
try {
$this->value = trim($this->value);
if (! str_ends_with($this->value, "\n")) {
$this->value .= "\n";
}
$private_key = PrivateKey::create([
$privateKey = PrivateKey::createAndStore([
'name' => $this->name,
'description' => $this->description,
'private_key' => $this->value,
'private_key' => trim($this->value)."\n",
'team_id' => currentTeam()->id,
]);
if ($this->from === 'server') {
return redirect()->route('dashboard');
}
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
return $this->redirectAfterCreation($privateKey);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function setKeyData(array $keyData)
{
$this->name = $keyData['name'];
$this->description = $keyData['description'];
$this->value = $keyData['private_key'];
$this->publicKey = $keyData['public_key'];
}
private function validatePrivateKey()
{
$validationResult = PrivateKey::validateAndExtractPublicKey($this->value);
$this->publicKey = $validationResult['publicKey'];
if (! $validationResult['isValid']) {
$this->addError('value', 'Invalid private key');
}
}
private function redirectAfterCreation(PrivateKey $privateKey)
{
return $this->from === 'server'
? redirect()->route('dashboard')
: redirect()->route('security.private-key.show', ['private_key_uuid' => $privateKey->uuid]);
}
}

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