Compare commits

...

135 Commits

Author SHA1 Message Date
Andras Bacsai
f16d0f650f Merge pull request #1596 from coollabsio/next
v4.0.0-beta.178
2024-01-02 17:23:11 +01:00
Andras Bacsai
cb80341a78 Fix customLabels assignment when proxyType is TRAEFIK_V2 2024-01-02 17:22:44 +01:00
Andras Bacsai
83d96c8d11 Refactor custom labels handling in General.php and update Docker Compose Content label in general.blade.php 2024-01-02 17:14:52 +01:00
Andras Bacsai
a8ca57d095 Update link in helper message in general.blade.php 2024-01-02 16:47:05 +01:00
Andras Bacsai
2d936a4b22 add compose link 2024-01-02 16:46:08 +01:00
Andras Bacsai
0653eb8511 set custom labels on every app 2024-01-02 16:44:41 +01:00
Andras Bacsai
cc64132627 Update README.md 2024-01-02 14:06:08 +01:00
Andras Bacsai
e0391e5abd Merge pull request #1594 from coollabsio/next
v4.0.0-beta.177
2024-01-02 13:58:30 +01:00
Andras Bacsai
025135bd2a feat: raw docker compose deployments 2024-01-02 13:55:35 +01:00
Andras Bacsai
5e5873a08d fix: duplicate compose variable 2024-01-02 11:46:02 +01:00
Andras Bacsai
14652c2878 Update sponsor logos and links 2024-01-02 07:31:54 +01:00
Andras Bacsai
a7ef5c456d Merge pull request #1592 from coollabsio/next
v4.0.0-beta.176
2023-12-31 10:52:15 +01:00
Andras Bacsai
117f1d4155 fix: horizon 2023-12-30 15:30:57 +01:00
Andras Bacsai
075f3ce930 remove unnecessary queue 2023-12-30 15:29:26 +01:00
Andras Bacsai
d5b3e88fc4 Merge pull request #1576 from coollabsio/next
v4.0.0-beta.175
2023-12-30 15:00:05 +01:00
Andras Bacsai
ba55e0c1bb feat: add environment description + able to change name 2023-12-30 14:47:26 +01:00
Andras Bacsai
54671354f0 fix: deploy key + docker compose 2023-12-30 14:20:02 +01:00
Andras Bacsai
a2c7e8d455 Merge pull request #1566 from stooit/fix/deploy-key-docker-compose
fix: Resolves deployment of docker compose applications when using a deploy key
2023-12-30 13:27:25 +01:00
Andras Bacsai
32dbdf5204 Fix redirect route in DecideWhatToDoWithUser middleware 2023-12-28 22:26:21 +01:00
Andras Bacsai
019887739c fix: wrong env variable parsing 2023-12-28 17:53:47 +01:00
Andras Bacsai
5596e41f2b fix: sub 2023-12-28 13:43:03 +01:00
Andras Bacsai
6a73a00a1f Merge pull request #1575 from coollabsio/next
v4.0.0-beta.174
2023-12-27 23:09:14 +01:00
Andras Bacsai
4121c9dd8b fix 2023-12-27 23:07:39 +01:00
Andras Bacsai
e4781dc129 fix: restore falsely deleted coolify-db-backup 2023-12-27 23:06:22 +01:00
Andras Bacsai
1c90f46f2a Merge pull request #1572 from coollabsio/next
v4.0.0-beta.173
2023-12-27 17:44:07 +01:00
Andras Bacsai
e78d851c85 fix: button title 2023-12-27 17:14:18 +01:00
Andras Bacsai
52d05005ed fix: deploy instead of restart in case swarm is used 2023-12-27 17:12:09 +01:00
Andras Bacsai
f03aa57758 fix: routing, switch back to old one 2023-12-27 16:45:01 +01:00
Andras Bacsai
8c20c833ba fix: add source commit to final envs 2023-12-27 13:06:59 +01:00
Andras Bacsai
2fe6766b7f fix: cpu limit to float from int 2023-12-27 13:01:57 +01:00
Stuart Rowlands
d9599da4a8 Fix git clone command for deploy key + docker compose. 2023-12-21 11:16:03 -08:00
Andras Bacsai
3f453ba7c0 Merge branch 'main' into next 2023-12-21 14:09:13 +01:00
Andras Bacsai
bd02c3055a update readme 2023-12-21 14:08:39 +01:00
andrasbacsai
7ea7d85d15 Deploying to main from @ coollabsio/coolify@d38350c282 🚀 2023-12-21 12:58:10 +00:00
Andras Bacsai
d38350c282 update 2023-12-21 13:57:52 +01:00
andrasbacsai
a83c70004c Deploying to main from @ coollabsio/coolify@49e1404a2c 🚀 2023-12-21 12:56:00 +00:00
Andras Bacsai
49e1404a2c test gh auto sponsor update 2023-12-21 13:55:40 +01:00
Andras Bacsai
76f23e7dbf do not check server ready on server status job 2023-12-21 10:43:39 +01:00
Andras Bacsai
ad8653f54d Merge pull request #1564 from coollabsio/next
v4.0.0-beta.172
2023-12-21 10:35:40 +01:00
Andras Bacsai
8939d77051 fix 2023-12-21 10:28:02 +01:00
Andras Bacsai
37be4a1796 fix 2023-12-21 10:00:41 +01:00
Andras Bacsai
e4c923e358 fix 2023-12-21 09:57:39 +01:00
Andras Bacsai
62ca3ffaa5 fix 2023-12-21 09:55:16 +01:00
Andras Bacsai
9af3ce4be5 fail job instead of runtime exception 2023-12-21 09:49:18 +01:00
Andras Bacsai
fe143ef8a5 Merge pull request #1563 from coollabsio/next
v4.0.0-beta.171
2023-12-21 09:35:43 +01:00
Andras Bacsai
5fb5845e90 redirect false on some urls 2023-12-21 09:33:11 +01:00
Andras Bacsai
794cfbd8eb execute handle on containerstatusjob 2023-12-21 09:28:47 +01:00
Andras Bacsai
29ee9915f3 fix: check proxy after mount on server view
fix: change realtime console log
version++
2023-12-21 09:28:39 +01:00
Andras Bacsai
331d485213 Merge pull request #1562 from coollabsio/next
v4.0.0-beta.170
2023-12-21 08:49:22 +01:00
Andras Bacsai
665e3761c4 fix: stay tuned 2023-12-21 08:48:53 +01:00
Andras Bacsai
ac19f0e34f enable docker image swarms 2023-12-21 08:46:48 +01:00
Andras Bacsai
d7cfa0578f Comment out handle() method call in
ContainerStatusJob constructor
2023-12-20 20:01:45 +01:00
Andras Bacsai
694169bb84 fix: why?! 2023-12-20 18:09:01 +01:00
Andras Bacsai
ab853cac87 Merge pull request #1560 from coollabsio/next
v4.0.0-beta.169
2023-12-20 16:21:06 +01:00
Andras Bacsai
51db2f797d fix: storage error on dbs 2023-12-20 16:19:48 +01:00
Andras Bacsai
0c90d3d0a1 fix: docker compose apps env rewritten 2023-12-20 16:15:13 +01:00
Andras Bacsai
51efe23690 Merge pull request #1559 from coollabsio/next
disable db + service deployments swarm
2023-12-20 14:46:12 +01:00
Andras Bacsai
e3ee84105c disable db + service deployments swarm 2023-12-20 14:45:47 +01:00
Andras Bacsai
6cbd61ac6c Merge pull request #1558 from coollabsio/next
fix: get swarm service logs
2023-12-20 14:25:53 +01:00
Andras Bacsai
638d0c8c99 fix: get swarm service logs 2023-12-20 14:11:50 +01:00
Andras Bacsai
aecc81fe9d Merge pull request #1557 from coollabsio/next
v4.0.0-beta.166
2023-12-20 13:21:45 +01:00
Andras Bacsai
c9a1437870 Fix handle method in ServerStatusJob 2023-12-20 12:33:58 +01:00
Andras Bacsai
66b41b3d4c Update ServerStatusJob middleware and uniqueId()
method
2023-12-20 12:33:21 +01:00
Andras Bacsai
c41cfe2a2f Fix server status check and cleanup logic 2023-12-20 12:32:46 +01:00
Andras Bacsai
5f2ad56529 Update container and server status job 2023-12-20 12:25:14 +01:00
Andras Bacsai
cd842bc1b2 Update number of tries in ContainerStatusJob 2023-12-20 12:13:34 +01:00
Andras Bacsai
27b6aad53a fix 2023-12-20 11:59:53 +01:00
Andras Bacsai
64b58b7661 hm 2023-12-20 11:59:06 +01:00
Andras Bacsai
94960d96a9 add max horizon processes 2023-12-20 11:47:51 +01:00
Andras Bacsai
2549244f97 hm 2023-12-20 11:44:46 +01:00
Andras Bacsai
5bfffce33b hm 2023-12-20 11:37:04 +01:00
Andras Bacsai
3a4f19f368 version++ 2023-12-20 11:22:10 +01:00
Andras Bacsai
50e17ed932 fix: server ready 2023-12-20 11:21:17 +01:00
Andras Bacsai
a8fcd7aee4 Merge pull request #1556 from coollabsio/next
small UI fixes
2023-12-20 10:31:59 +01:00
Andras Bacsai
87036cc49b link 2023-12-20 10:27:39 +01:00
Andras Bacsai
e48842c6ec fix: swarm support ui 2023-12-20 10:19:21 +01:00
Andras Bacsai
b9e405c497 Merge pull request #1555 from coollabsio/next
v4.0.0-beta.165
2023-12-19 21:01:40 +01:00
Andras Bacsai
f75effe022 fix 2023-12-19 15:44:41 +01:00
Andras Bacsai
a745f568f3 gh actions update 2023-12-19 15:44:01 +01:00
Andras Bacsai
e2e3ad0358 get branchname gh actions 2023-12-19 15:41:53 +01:00
Andras Bacsai
ba769f5fb7 fix 2023-12-19 15:36:59 +01:00
Andras Bacsai
0126286731 fix: server update schedule 2023-12-19 15:16:08 +01:00
Andras Bacsai
7952202435 fix: do not autovalidate server on mount 2023-12-19 14:19:23 +01:00
Andras Bacsai
798acb8ee5 add swarm server grouping
fixes for swarm
2023-12-19 13:47:12 +01:00
Andras Bacsai
ef595dd4c2 fix: server not found 2023-12-19 12:24:43 +01:00
Andras Bacsai
70c662daf8 disable swarm for the next release 2023-12-18 17:13:22 +01:00
Andras Bacsai
8ae385b9f9 fix: add alpha to swarm 2023-12-18 14:34:04 +01:00
Andras Bacsai
802a0f7684 fix: do not push dockerimage 2023-12-18 14:17:48 +01:00
Andras Bacsai
62c38c9859 wip: swarm 2023-12-18 14:01:25 +01:00
Andras Bacsai
27c36bec83 feat: custom docker compose commands 2023-12-17 20:56:12 +01:00
Andras Bacsai
c6b8eabe10 wip: swarm 2023-12-15 15:48:01 +01:00
Andras Bacsai
967fca9eca version ++ 2023-12-15 15:17:49 +01:00
Andras Bacsai
40a239ddda Merge pull request #1550 from coollabsio/next
refactor: gitlab manual webhooks
2023-12-15 14:19:49 +01:00
Andras Bacsai
99d07981cf fix 2023-12-15 14:19:29 +01:00
Andras Bacsai
b3ee6b7144 fix: add debug output to gitlab webhooks 2023-12-15 14:17:53 +01:00
Andras Bacsai
468ad7d904 fix: no action in webhooks 2023-12-15 14:09:14 +01:00
Andras Bacsai
f1aa97e374 Merge pull request #1548 from coollabsio/next
fix: compose domains & links
2023-12-15 11:01:03 +01:00
Andras Bacsai
3b6d3343c7 fix: domains for compose bp 2023-12-15 11:00:51 +01:00
Andras Bacsai
ab2f9f073f Merge pull request #1546 from stooit/fix/docker-compose-multidomain
fix: Multiple domain links in docker compose applications.
2023-12-15 10:39:01 +01:00
Andras Bacsai
67131152cc fix: reset domains on compose file change 2023-12-15 10:37:45 +01:00
Andras Bacsai
3bda289428 fix: ui for adding new destination 2023-12-15 10:24:02 +01:00
Andras Bacsai
fadfa0ad8e Merge pull request #1547 from coollabsio/next
fix: server checking status
2023-12-15 10:01:58 +01:00
Andras Bacsai
11a957c6c9 fix: server checking status 2023-12-15 10:01:14 +01:00
Stuart Rowlands
b46de99af9 Fixes multi-domain links in docker compose applications. 2023-12-14 18:16:17 -08:00
Andras Bacsai
03420076c9 Merge pull request #1545 from coollabsio/next
v4.0.0-beta.163
2023-12-14 15:41:16 +01:00
Andras Bacsai
549446abdf fix: handle other types of generated values 2023-12-14 15:34:05 +01:00
Andras Bacsai
06ab2145ca fix: improve server status check times 2023-12-14 15:33:46 +01:00
Andras Bacsai
5d088e530e version++ 2023-12-14 15:33:34 +01:00
Andras Bacsai
123e6eddd7 fix: only check server status in container status job 2023-12-14 15:33:25 +01:00
Andras Bacsai
2c17e431ac Merge pull request #1544 from coollabsio/next
fix: backup executions view
2023-12-14 15:01:35 +01:00
Andras Bacsai
fe6073ba7d fix: backup executions view 2023-12-14 15:01:19 +01:00
Andras Bacsai
8a9ad04744 Merge pull request #1540 from coollabsio/next
v4.0.0-beta.162
2023-12-14 14:58:30 +01:00
Andras Bacsai
75d1ec4f42 feat: pull latest images for services 2023-12-14 14:50:38 +01:00
Andras Bacsai
a0abde8652 ui: add image name to service stack + better options visibility 2023-12-14 14:24:54 +01:00
Andras Bacsai
db13dd9304 fix: revert random container job delay 2023-12-13 15:40:57 +01:00
Andras Bacsai
638bcf9732 update 2023-12-13 15:34:33 +01:00
Andras Bacsai
b06b465ffa fix: add catch all route 2023-12-13 15:29:45 +01:00
Andras Bacsai
02c8b9f471 fix: password reset / invitation link requests 2023-12-13 15:22:37 +01:00
Andras Bacsai
1ff1664b6c fix: copy invitation 2023-12-13 14:44:11 +01:00
Andras Bacsai
52d84c5e9e refactor: clone project 2023-12-13 14:22:23 +01:00
Andras Bacsai
e0289e2949 feat: randomly sleep between executions 2023-12-13 12:35:56 +01:00
Andras Bacsai
ff8d8371ad fix: check queued deployments as well 2023-12-13 12:13:20 +01:00
Andras Bacsai
69343f974a soft delete models 2023-12-13 12:08:12 +01:00
Andras Bacsai
2dc175be63 fix: null notify 2023-12-13 12:01:27 +01:00
Andras Bacsai
d93bf97919 cleanup on start 2023-12-13 12:01:21 +01:00
Andras Bacsai
4ea8916d53 fix: update Coolify script 2023-12-13 11:55:08 +01:00
Andras Bacsai
f7fca69a23 update sentry key 2023-12-13 11:53:50 +01:00
Andras Bacsai
f954ee15c3 fix: init script echos 2023-12-13 11:53:01 +01:00
Andras Bacsai
3c54e01d87 improve more 2023-12-13 11:35:53 +01:00
Andras Bacsai
00d708610d improve local dev + contribution guide 2023-12-13 11:12:53 +01:00
Andras Bacsai
f3b04c1ef9 refactor: custom labels 2023-12-13 09:23:27 +01:00
Andras Bacsai
6b751f965b Merge pull request #1539 from coollabsio/next
v4.0.0-beta.161
2023-12-12 16:47:56 +01:00
Andras Bacsai
d3c9894479 fix: labels 2023-12-12 16:45:46 +01:00
Andras Bacsai
f68aace445 fix: non-ascii chars in labels 2023-12-12 16:34:05 +01:00
Andras Bacsai
f042c70b3c fix: labelling 2023-12-12 15:48:51 +01:00
177 changed files with 2934 additions and 1234 deletions

View File

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

View File

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

View File

@@ -17,6 +17,40 @@ https://coolify.io/sponsorships
Thank you so much! Thank you so much!
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
## Github Sponsors ($15+)
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://github.com/Illyism"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
## Organizations
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
# Cloud # Cloud
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
@@ -59,34 +93,6 @@ Contact us [here](https://coolify.io/docs/contact).
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
# 💰 Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
## Organizations
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="appwrite logo" width="200"/></a>
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
Support this project with your organization. Your logo will show up here with a link to your website.
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
## Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
# Star History # Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date) [![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

View File

@@ -56,7 +56,7 @@ class StartMariadb
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness, 'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset, 'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ]

View File

@@ -63,7 +63,7 @@ class StartMongodb
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness, 'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset, 'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ]

View File

@@ -56,7 +56,7 @@ class StartMysql
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness, 'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset, 'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ]

View File

@@ -66,7 +66,7 @@ class StartPostgresql
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness, 'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset, 'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ]

View File

@@ -65,7 +65,7 @@ class StartRedis
'memswap_limit' => $this->database->limits_memory_swap, 'memswap_limit' => $this->database->limits_memory_swap,
'mem_swappiness' => $this->database->limits_memory_swappiness, 'mem_swappiness' => $this->database->limits_memory_swappiness,
'mem_reservation' => $this->database->limits_memory_reservation, 'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (int) $this->database->limits_cpus, 'cpus' => (float) $this->database->limits_cpus,
'cpuset' => $this->database->limits_cpuset, 'cpuset' => $this->database->limits_cpuset,
'cpu_shares' => $this->database->limits_cpu_shares, 'cpu_shares' => $this->database->limits_cpu_shares,
] ]

View File

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

View File

@@ -18,7 +18,7 @@ class UpdateCoolify
try { try {
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob'); ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0)->first(); $this->server = Server::find(0);
if (!$this->server) { if (!$this->server) {
return; return;
} }

View File

@@ -11,6 +11,7 @@ class StartService
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Starting service: ' . $service->name);
$network = $service->destination->network; $network = $service->destination->network;
$service->saveComposeConfigs(); $service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir(); $commands[] = "cd " . $service->workdir();

View File

@@ -10,6 +10,7 @@ class StopService
use AsAction; use AsAction;
public function handle(Service $service) public function handle(Service $service)
{ {
ray('Stopping service: ' . $service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server); instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server; use App\Models\Server;
use App\Models\Service; use App\Models\Service;
use App\Models\ServiceApplication; use App\Models\ServiceApplication;
@@ -31,6 +32,9 @@ class Init extends Command
if ($cleanup) { if ($cleanup) {
echo "Running cleanup\n"; echo "Running cleanup\n";
$this->cleanup_stucked_resources(); $this->cleanup_stucked_resources();
// Required for falsely deleted coolify db
$this->restore_coolify_db_backup();
// $this->cleanup_ssh(); // $this->cleanup_ssh();
} }
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
@@ -51,6 +55,30 @@ class Init extends Command
} }
} }
} }
private function restore_coolify_db_backup() {
try {
$database = StandalonePostgresql::withTrashed()->find(0);
if ($database && $database->trashed()) {
echo "Restoring coolify db backup\n";
$database->restore();
$scheduledBackup = ScheduledDatabaseBackup::find(0);
if (!$scheduledBackup) {
ScheduledDatabaseBackup::create([
'id' => 0,
'enabled' => true,
'save_s3' => false,
'frequency' => '0 0 * * *',
'database_id' => $database->id,
'database_type' => 'App\Models\StandalonePostgresql',
'team_id' => 0,
]);
}
}
} catch(\Throwable $e) {
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_helper_containers() private function cleanup_stucked_helper_containers()
{ {
$servers = Server::all(); $servers = Server::all();
@@ -99,7 +127,7 @@ class Init extends Command
// Cleanup any failed deployments // Cleanup any failed deployments
try { try {
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get(); $halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
foreach ($halted_deployments as $deployment) { foreach ($halted_deployments as $deployment) {
$deployment->status = ApplicationDeploymentStatus::FAILED->value; $deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save(); $deployment->save();
@@ -115,35 +143,41 @@ class Init extends Command
$applications = Application::all(); $applications = Application::all();
foreach ($applications as $application) { foreach ($applications as $application) {
if (!data_get($application, 'environment')) { if (!data_get($application, 'environment')) {
echo 'Application without environment' . $application->name . 'deleting\n'; echo 'Application without environment: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
if (!$application->destination()) { if (!$application->destination()) {
echo 'Application without destination' . $application->name . 'deleting\n'; echo 'Application without destination: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
if (!data_get($application, 'destination.server')) { if (!data_get($application, 'destination.server')) {
echo 'Application without server' . $application->name . 'deleting\n'; echo 'Application without server: ' . $application->name . ' soft deleting\n';
$application->delete(); $application->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
echo "Error in application: {$e->getMessage()}\n"; echo "Error in application: {$e->getMessage()}\n";
} }
try { try {
$postgresqls = StandalonePostgresql::all(); $postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
foreach ($postgresqls as $postgresql) { foreach ($postgresqls as $postgresql) {
if (!data_get($postgresql, 'environment')) { if (!data_get($postgresql, 'environment')) {
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n'; echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
if (!$postgresql->destination()) { if (!$postgresql->destination()) {
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n'; echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
if (!data_get($postgresql, 'destination.server')) { if (!data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server' . $postgresql->name . 'deleting\n'; echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
$postgresql->delete(); $postgresql->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -153,16 +187,19 @@ class Init extends Command
$redis = StandaloneRedis::all(); $redis = StandaloneRedis::all();
foreach ($redis as $redis) { foreach ($redis as $redis) {
if (!data_get($redis, 'environment')) { if (!data_get($redis, 'environment')) {
echo 'Redis without environment' . $redis->name . 'deleting\n'; echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
if (!$redis->destination()) { if (!$redis->destination()) {
echo 'Redis without destination' . $redis->name . 'deleting\n'; echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
if (!data_get($redis, 'destination.server')) { if (!data_get($redis, 'destination.server')) {
echo 'Redis without server' . $redis->name . 'deleting\n'; echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
$redis->delete(); $redis->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -173,16 +210,19 @@ class Init extends Command
$mongodbs = StandaloneMongodb::all(); $mongodbs = StandaloneMongodb::all();
foreach ($mongodbs as $mongodb) { foreach ($mongodbs as $mongodb) {
if (!data_get($mongodb, 'environment')) { if (!data_get($mongodb, 'environment')) {
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n'; echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
if (!$mongodb->destination()) { if (!$mongodb->destination()) {
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n'; echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
if (!data_get($mongodb, 'destination.server')) { if (!data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server' . $mongodb->name . 'deleting\n'; echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
$mongodb->delete(); $mongodb->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -193,16 +233,19 @@ class Init extends Command
$mysqls = StandaloneMysql::all(); $mysqls = StandaloneMysql::all();
foreach ($mysqls as $mysql) { foreach ($mysqls as $mysql) {
if (!data_get($mysql, 'environment')) { if (!data_get($mysql, 'environment')) {
echo 'Mysql without environment' . $mysql->name . 'deleting\n'; echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
if (!$mysql->destination()) { if (!$mysql->destination()) {
echo 'Mysql without destination' . $mysql->name . 'deleting\n'; echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
if (!data_get($mysql, 'destination.server')) { if (!data_get($mysql, 'destination.server')) {
echo 'Mysql without server' . $mysql->name . 'deleting\n'; echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
$mysql->delete(); $mysql->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -213,16 +256,19 @@ class Init extends Command
$mariadbs = StandaloneMariadb::all(); $mariadbs = StandaloneMariadb::all();
foreach ($mariadbs as $mariadb) { foreach ($mariadbs as $mariadb) {
if (!data_get($mariadb, 'environment')) { if (!data_get($mariadb, 'environment')) {
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n'; echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
if (!$mariadb->destination()) { if (!$mariadb->destination()) {
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n'; echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
if (!data_get($mariadb, 'destination.server')) { if (!data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server' . $mariadb->name . 'deleting\n'; echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
$mariadb->delete(); $mariadb->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -233,16 +279,19 @@ class Init extends Command
$services = Service::all(); $services = Service::all();
foreach ($services as $service) { foreach ($services as $service) {
if (!data_get($service, 'environment')) { if (!data_get($service, 'environment')) {
echo 'Service without environment' . $service->name . 'deleting\n'; echo 'Service without environment: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
if (!$service->destination()) { if (!$service->destination()) {
echo 'Service without destination' . $service->name . 'deleting\n'; echo 'Service without destination: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
if (!data_get($service, 'server')) { if (!data_get($service, 'server')) {
echo 'Service without server' . $service->name . 'deleting\n'; echo 'Service without server: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -252,8 +301,9 @@ class Init extends Command
$serviceApplications = ServiceApplication::all(); $serviceApplications = ServiceApplication::all();
foreach ($serviceApplications as $service) { foreach ($serviceApplications as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceApplication without service' . $service->name . 'deleting\n'; echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -263,8 +313,9 @@ class Init extends Command
$serviceDatabases = ServiceDatabase::all(); $serviceDatabases = ServiceDatabase::all();
foreach ($serviceDatabases as $service) { foreach ($serviceDatabases as $service) {
if (!data_get($service, 'service')) { if (!data_get($service, 'service')) {
echo 'ServiceDatabase without service' . $service->name . 'deleting\n'; echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
$service->delete(); $service->delete();
continue;
} }
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -56,16 +56,20 @@ class Kernel extends ConsoleKernel
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers; $own = Team::find(0)->servers;
$servers = $servers->merge($own); $servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false);
} else { } else {
$servers = Server::all()->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false);
} }
foreach ($servers as $server) { foreach ($containerServers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
if ($server->isLogDrainEnabled()) { if ($server->isLogDrainEnabled()) {
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer(); $schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
} }
} }
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
}
} }
private function instance_auto_update($schedule) private function instance_auto_update($schedule)
{ {

View File

@@ -47,7 +47,7 @@ class Handler extends ExceptionHandler
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) { if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401); return response()->json(['message' => $exception->getMessage()], 401);
} }
return redirect()->guest($exception->redirectTo() ?? route('login')); return redirect()->guest($exception->redirectTo() ?? route('login'));
} }
/** /**
* Register the exception handling callbacks for the application. * Register the exception handling callbacks for the application.

View File

@@ -39,7 +39,7 @@ class Controller extends BaseController
} else { } else {
$team = $user->teams()->first(); $team = $user->teams()->first();
} }
if (is_null(data_get($user, 'email_verified_at'))){ if (is_null(data_get($user, 'email_verified_at'))) {
$user->email_verified_at = now(); $user->email_verified_at = now();
$user->save(); $user->save();
} }
@@ -137,16 +137,28 @@ class Controller extends BaseController
public function acceptInvitation() public function acceptInvitation()
{ {
try { try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail(); $resetPassword = request()->query('reset-password');
$invitationUuid = request()->route('uuid');
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail(); $user = User::whereEmail($invitation->email)->firstOrFail();
if (auth()->user()->id !== $user->id) {
abort(401);
}
$invitationValid = $invitation->isValid(); $invitationValid = $invitation->isValid();
if ($invitationValid) { if ($invitationValid) {
if ($resetPassword) {
$user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete();
return redirect()->route('team.index');
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
refreshSession($invitation->team);
$invitation->delete(); $invitation->delete();
if (auth()->user()?->id !== $user->id) {
return redirect()->route('login');
}
refreshSession($invitation->team);
return redirect()->route('team.index'); return redirect()->route('team.index');
} else { } else {
abort(401); abort(401);

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,6 +75,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $docker_compose_base64; private $docker_compose_base64;
private string $dockerfile_location = '/Dockerfile'; private string $dockerfile_location = '/Dockerfile';
private string $docker_compose_location = '/docker-compose.yml'; private string $docker_compose_location = '/docker-compose.yml';
private ?string $docker_compose_custom_start_command = null;
private ?string $docker_compose_custom_build_command = null;
private ?string $addHosts = null; private ?string $addHosts = null;
private ?string $buildTarget = null; private ?string $buildTarget = null;
private Collection $saved_outputs; private Collection $saved_outputs;
@@ -215,19 +217,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server)); dispatch(new ContainerStatusJob($this->server));
} }
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') { if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && !$this->application->destination->server->isSwarm()) {
$this->push_to_docker_registry(); $this->push_to_docker_registry();
if ($this->server->isSwarm()) {
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}")
],
[
"echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'"
]
);
}
} }
$this->next(ApplicationDeploymentStatus::FINISHED->value); $this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(true); $this->application->isConfigurationChanged(true);
@@ -267,7 +258,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true, "ignore_errors" => true,
] ]
); );
ApplicationStatusChanged::dispatch(data_get($this->application,'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
} }
} }
private function push_to_docker_registry() private function push_to_docker_registry()
@@ -299,6 +290,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"echo -n 'Image pushed to docker registry.'" "echo -n 'Image pushed to docker registry.'"
]); ]);
} catch (Exception $e) { } catch (Exception $e) {
if ($this->application->destination->server->isSwarm()) {
throw $e;
}
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"], ["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"],
); );
@@ -432,6 +426,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (data_get($this->application, 'docker_compose_location')) { if (data_get($this->application, 'docker_compose_location')) {
$this->docker_compose_location = $this->application->docker_compose_location; $this->docker_compose_location = $this->application->docker_compose_location;
} }
if (data_get($this->application, 'docker_compose_custom_start_command')) {
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
}
if (data_get($this->application, 'docker_compose_custom_build_command')) {
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
}
if ($this->pull_request_id === 0) { if ($this->pull_request_id === 0) {
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
} else { } else {
@@ -446,15 +446,30 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_image_names(); $this->generate_image_names();
$this->cleanup_git(); $this->cleanup_git();
$this->application->loadComposeFile(isInit: false); $this->application->loadComposeFile(isInit: false);
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); if ($this->application->settings->is_raw_compose_deployment_enabled) {
$yaml = Yaml::dump($composeFile->toArray(), 10); $yaml = $composeFile = $this->application->docker_compose_raw;
} else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
$yaml = Yaml::dump($composeFile->toArray(), 10);
}
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
]); ]);
$this->save_environment_variables(); $this->save_environment_variables();
// Build new container to limit downtime. // Build new container to limit downtime.
$this->build_by_compose_file(); $this->application_deployment_queue->addLogEntry("Pulling & building required images.");
if ($this->docker_compose_custom_build_command) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
);
}
$this->stop_running_container(force: true); $this->stop_running_container(force: true);
$networkId = $this->application->uuid; $networkId = $this->application->uuid;
@@ -488,7 +503,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
] ]
); );
} }
$this->start_by_compose_file(); // Start compose file
if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
}
$this->application_deployment_queue->addLogEntry("New container started.");
} }
private function deploy_dockerfile_buildpack() private function deploy_dockerfile_buildpack()
{ {
@@ -575,7 +600,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update() private function rolling_update()
{ {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
// Skip this. if ($this->build_pack !== 'dockerimage') {
$this->push_to_docker_registry();
}
$this->application_deployment_queue->addLogEntry("Rolling update started.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}")
],
);
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
} else { } else {
if (count($this->application->ports_mappings_array) > 0) { if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command( $this->execute_remote_command(
@@ -674,10 +708,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->add_build_env_variables_to_dockerfile(); $this->add_build_env_variables_to_dockerfile();
$this->build_image(); $this->build_image();
$this->stop_running_container(); $this->stop_running_container();
$this->execute_remote_command( if ($this->application->destination->server->isSwarm()) {
["echo -n 'Starting preview deployment.'"], ray("{$this->workdir}{$this->docker_compose_location}");
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], $this->push_to_docker_registry();
); $this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
],
);
} else {
$this->execute_remote_command(
["echo -n 'Starting preview deployment.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
);
}
} }
private function create_workdir() private function create_workdir()
{ {
@@ -874,6 +918,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables = $this->generate_environment_variables($ports); $environment_variables = $this->generate_environment_variables($ports);
if (data_get($this->application, 'custom_labels')) { if (data_get($this->application, 'custom_labels')) {
$this->application->parseContainerLabels();
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels))); $labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
$labels = $labels->filter(function ($value, $key) { $labels = $labels->filter(function ($value, $key) {
return !Str::startsWith($value, 'coolify.'); return !Str::startsWith($value, 'coolify.');
@@ -913,7 +958,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'memswap_limit' => $this->application->limits_memory_swap, 'memswap_limit' => $this->application->limits_memory_swap,
'mem_swappiness' => $this->application->limits_memory_swappiness, 'mem_swappiness' => $this->application->limits_memory_swappiness,
'mem_reservation' => $this->application->limits_memory_reservation, 'mem_reservation' => $this->application->limits_memory_reservation,
'cpus' => (int) $this->application->limits_cpus, 'cpus' => (float) $this->application->limits_cpus,
'cpuset' => $this->application->limits_cpuset, 'cpuset' => $this->application->limits_cpuset,
'cpu_shares' => $this->application->limits_cpu_shares, 'cpu_shares' => $this->application->limits_cpu_shares,
] ]
@@ -940,13 +985,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares'); data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
$docker_compose['services'][$this->container_name]['deploy'] = [ $docker_compose['services'][$this->container_name]['deploy'] = [
'placement' => [
'constraints' => [
'node.role == worker'
]
],
'mode' => 'replicated', 'mode' => 'replicated',
'replicas' => 1, 'replicas' => data_get($this->application, 'swarm_replicas', 1),
'update_config' => [ 'update_config' => [
'order' => 'start-first' 'order' => 'start-first'
], ],
@@ -965,6 +1005,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
] ]
] ]
]; ];
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
'constraints' => [
'node.role == worker'
]
];
}
if ($this->pull_request_id !== 0) {
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
}
} else { } else {
$docker_compose['services'][$this->container_name]['labels'] = $labels; $docker_compose['services'][$this->container_name]['labels'] = $labels;
} }
@@ -1083,6 +1133,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Add PORT if not exists, use the first port as default // Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) { if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}"); $environment_variables->push("PORT={$ports[0]}");
} if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
}
} }
return $environment_variables->all(); return $environment_variables->all();
} }
@@ -1247,7 +1301,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
} }
private function build_by_compose_file() { private function build_by_compose_file()
{
$this->application_deployment_queue->addLogEntry("Pulling & building required images."); $this->application_deployment_queue->addLogEntry("Pulling & building required images.");
if ($this->application->build_pack === 'dockerimage') { if ($this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry."); $this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
@@ -1256,15 +1311,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
); );
} else { } else {
if ($this->docker_compose_location) { $this->execute_remote_command(
$this->execute_remote_command( [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true], );
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
);
}
} }
$this->application_deployment_queue->addLogEntry("New images built."); $this->application_deployment_queue->addLogEntry("New images built.");
} }
@@ -1339,10 +1388,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
queue_next_deployment($this->application); queue_next_deployment($this->application);
if ($status === ApplicationDeploymentStatus::FINISHED->value) { if ($status === ApplicationDeploymentStatus::FINISHED->value) {
$this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
} }
if ($status === ApplicationDeploymentStatus::FAILED->value) { if ($status === ApplicationDeploymentStatus::FAILED->value) {
$this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); $this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
} }
} }

View File

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

View File

@@ -22,14 +22,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->id))->dontRelease()]; return [(new WithoutOverlapping($this->server->uuid))];
} }
public function uniqueId(): int public function uniqueId(): int
{ {
return $this->server->id; return $this->server->uuid;
} }
public function __construct(public Server $server) public function __construct(public Server $server)
@@ -37,17 +42,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$this->handle(); $this->handle();
} }
public function handle() public function handle()
{ {
// ray("checking container statuses for {$this->server->id}"); if (!$this->server->isServerReady($this->tries)) {
return 'Server is not reachable.';
};
try { try {
if (!$this->server->isServerReady()) {
return;
};
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false); $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else { } else {
// Precheck for containers // Precheck for containers
$containers = instant_remote_process(["docker container ls -q"], $this->server, false); $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
@@ -55,15 +58,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
return; return;
} }
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
$containerReplicase = null; $containerReplicates = null;
} }
if (is_null($containers)) { if (is_null($containers)) {
return; return;
} }
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
if ($containerReplicase) { if ($containerReplicates) {
$containerReplicase = format_docker_command_output_to_json($containerReplicase); $containerReplicates = format_docker_command_output_to_json($containerReplicates);
foreach ($containerReplicase as $containerReplica) { foreach ($containerReplicates as $containerReplica) {
$name = data_get($containerReplica, 'Name'); $name = data_get($containerReplica, 'Name');
$containers = $containers->map(function ($container) use ($name, $containerReplica) { $containers = $containers->map(function ($container) use ($name, $containerReplica) {
if (data_get($container, 'Spec.Name') === $name) { if (data_get($container, 'Spec.Name') === $name) {

View File

@@ -286,7 +286,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
if ($this->backup->save_s3) { if ($this->backup->save_s3) {
$this->upload_to_s3(); $this->upload_to_s3();
} }
$this->team->notify(new BackupSuccess($this->backup, $this->database)); $this->team?->notify(new BackupSuccess($this->backup, $this->database));
$this->backup_log->update([ $this->backup_log->update([
'status' => 'success', 'status' => 'success',
'message' => $this->backup_output, 'message' => $this->backup_output,
@@ -302,7 +302,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]); ]);
} }
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
throw $e; throw $e;
} }
} }

View File

@@ -32,9 +32,10 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
try { try {
$server = $this->resource->destination->server; $server = $this->resource->destination->server;
if (!$server->isFunctional()) { if (!$server->isFunctional()) {
$this->resource->delete(); $this->resource->forceDelete();
return 'Server is not functional'; return 'Server is not functional';
} }
$this->resource->delete();
switch ($this->resource->type()) { switch ($this->resource->type()) {
case 'application': case 'application':
StopApplication::run($this->resource); StopApplication::run($this->resource);
@@ -56,10 +57,9 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
break; break;
} }
if ($this->resource->type() === 'service') { if ($this->resource->type() === 'service') {
$this->resource->delete();
DeleteService::dispatch($this->resource); DeleteService::dispatch($this->resource);
} else { } else {
$this->resource->delete(); $this->resource->forceDelete();
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage()); send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());

View File

@@ -17,30 +17,34 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?int $disk_usage = null; public ?int $disk_usage = null;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
} }
public function middleware(): array public function middleware(): array
{ {
return [(new WithoutOverlapping($this->server->id))->dontRelease()]; return [(new WithoutOverlapping($this->server->uuid))];
} }
public function uniqueId(): int public function uniqueId(): int
{ {
return $this->server->id; return $this->server->uuid;
} }
public function handle(): void public function handle()
{ {
ray("checking server status for {$this->server->id}");
try { try {
if ($this->server->isServerReady()) { if ($this->server->isFunctional()) {
$this->cleanup(notify: false); $this->cleanup(notify: false);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage()); send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
handleError($e); return handleError($e);
} }
} }
public function cleanup(bool $notify = false): void public function cleanup(bool $notify = false): void
@@ -54,7 +58,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$this->server->high_disk_usage_notification_sent = true; $this->server->high_disk_usage_notification_sent = true;
$this->server->save(); $this->server->save();
$this->server->team->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage)); $this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
} }
} else { } else {
DockerCleanupJob::dispatchSync($this->server); DockerCleanupJob::dispatchSync($this->server);

View File

@@ -37,7 +37,7 @@ class CheckLicense extends Component
} catch (\Throwable $e) { } catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage()); session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
return $this->redirect('/settings/license', navigate: true); return redirect()->route('settings.license');
} }
} }
} }

View File

@@ -36,7 +36,7 @@ class Form extends Component
instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server); instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server);
} }
$this->destination->delete(); $this->destination->delete();
return $this->redirectRoute('dashboard', navigate: true); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -4,29 +4,32 @@ namespace App\Livewire\Destination\New;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker; use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class StandaloneDocker extends Component class Docker extends Component
{ {
public string $name; public string $name;
public string $network; public string $network;
public Collection $servers; public Collection $servers;
public Server $server; public Server $server;
public int|null $server_id = null; public ?int $server_id = null;
public bool $is_swarm = false;
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'network' => 'required|string', 'network' => 'required|string',
'server_id' => 'required|integer' 'server_id' => 'required|integer',
'is_swarm' => 'boolean'
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'name', 'name' => 'name',
'network' => 'network', 'network' => 'network',
'server_id' => 'server' 'server_id' => 'server',
'is_swarm' => 'swarm'
]; ];
public function mount() public function mount()
@@ -43,13 +46,13 @@ class StandaloneDocker extends Component
} else { } else {
$this->network = new Cuid2(7); $this->network = new Cuid2(7);
} }
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}"); $this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
} }
public function generate_name() public function generate_name()
{ {
$this->server = Server::find($this->server_id); $this->server = Server::find($this->server_id);
$this->name = Str::kebab("{$this->server->name}-{$this->network}"); $this->name = str("{$this->server->name}-{$this->network}")->kebab();
} }
public function submit() public function submit()
@@ -57,20 +60,33 @@ class StandaloneDocker extends Component
$this->validate(); $this->validate();
try { try {
$this->server = Server::find($this->server_id); $this->server = Server::find($this->server_id);
$found = $this->server->standaloneDockers()->where('network', $this->network)->first(); if ($this->is_swarm) {
if ($found) { $found = $this->server->swarmDockers()->where('network', $this->network)->first();
$this->createNetworkAndAttachToProxy(); if ($found) {
$this->dispatch('error', 'Network already added to this server.'); $this->dispatch('error', 'Network already added to this server.');
return; return;
} else {
$docker = SwarmDocker::create([
'name' => $this->name,
'network' => $this->network,
'server_id' => $this->server_id,
]);
}
} else { } else {
$docker = ModelsStandaloneDocker::create([ $found = $this->server->standaloneDockers()->where('network', $this->network)->first();
'name' => $this->name, if ($found) {
'network' => $this->network, $this->dispatch('error', 'Network already added to this server.');
'server_id' => $this->server_id, return;
]); } else {
$docker = ModelsStandaloneDocker::create([
'name' => $this->name,
'network' => $this->network,
'server_id' => $this->server_id,
]);
}
} }
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
return $this->redirectRoute('destination.show', $docker->uuid, navigate: true); return redirect()->route('destination.show', $docker->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -13,7 +13,11 @@ class Show extends Component
public function scan() public function scan()
{ {
$alreadyAddedNetworks = $this->server->standaloneDockers; if ($this->server->isSwarm()) {
$alreadyAddedNetworks = $this->server->swarmDockers;
} else {
$alreadyAddedNetworks = $this->server->standaloneDockers;
}
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false); $networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) { $this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none'; return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';

View File

@@ -35,7 +35,7 @@ class ForcePasswordReset extends Component
if ($firstLogin) { if ($firstLogin) {
send_internal_notification('First login for ' . auth()->user()->email); send_internal_notification('First login for ' . auth()->user()->email);
} }
return $this->redirectRoute('dashboard', navigate: true); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -52,7 +52,7 @@ class DiscordSettings extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

@@ -70,7 +70,7 @@ class EmailSettings extends Component
} }
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test($this->emails)); $this->team?->notify(new Test($this->emails));
$this->dispatch('success', 'Test Email sent successfully.'); $this->dispatch('success', 'Test Email sent successfully.');
} }
public function instantSaveInstance() public function instantSaveInstance()

View File

@@ -58,7 +58,7 @@ class TelegramSettings extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->team->notify(new Test()); $this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.'); $this->dispatch('success', 'Test notification sent.');
} }
} }

View File

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

View File

@@ -67,9 +67,9 @@ class Create extends Component
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id
]); ]);
if ($this->from === 'server') { if ($this->from === 'server') {
return $this->redirectRoute('server.create', navigate: true); return redirect()->route('server.create');
} }
return $this->redirectRoute('security.private-key.show', ['private_key_uuid' => $private_key->uuid], navigate: true); return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -27,7 +27,7 @@ class AddEmpty extends Component
'description' => $this->description, 'description' => $this->description,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
return $this->redirectRoute('project.show', $project->uuid, navigate: true); return redirect()->route('project.show', $project->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@@ -27,10 +27,10 @@ class AddEnvironment extends Component
'project_id' => $this->project->id, 'project_id' => $this->project->id,
]); ]);
return $this->redirectRoute('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $this->project->uuid, 'project_uuid' => $this->project->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
handleError($e, $this); handleError($e, $this);
} finally { } finally {

View File

@@ -15,15 +15,15 @@ class Configuration extends Component
{ {
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (!$project) { if (!$project) {
return $this->redirectRoute('dashboard', navigate: true); return redirect()->route('dashboard');
} }
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
if (!$environment) { if (!$environment) {
return $this->redirectRoute('dashboard', navigate: true); return redirect()->route('dashboard');
} }
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first(); $application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
if (!$application) { if (!$application) {
return $this->redirectRoute('dashboard', navigate: true); return redirect()->route('dashboard');
} }
$this->application = $application; $this->application = $application;
$mainServer = $this->application->destination->server; $mainServer = $this->application->destination->server;

View File

@@ -64,6 +64,9 @@ class General extends Component
'application.custom_labels' => 'nullable', 'application.custom_labels' => 'nullable',
'application.dockerfile_target_build' => 'nullable', 'application.dockerfile_target_build' => 'nullable',
'application.settings.is_static' => 'boolean|required', 'application.settings.is_static' => 'boolean|required',
'application.docker_compose_custom_start_command' => 'nullable',
'application.docker_compose_custom_build_command' => 'nullable',
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'application.name' => 'name', 'application.name' => 'name',
@@ -94,6 +97,9 @@ class General extends Component
'application.custom_labels' => 'Custom labels', 'application.custom_labels' => 'Custom labels',
'application.dockerfile_target_build' => 'Dockerfile target build', 'application.dockerfile_target_build' => 'Dockerfile target build',
'application.settings.is_static' => 'Is static', 'application.settings.is_static' => 'Is static',
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
]; ];
public function mount() public function mount()
{ {
@@ -109,17 +115,12 @@ class General extends Component
$this->application->isConfigurationChanged(true); $this->application->isConfigurationChanged(true);
} }
$this->isConfigurationChanged = $this->application->isConfigurationChanged(); $this->isConfigurationChanged = $this->application->isConfigurationChanged();
$this->customLabels = $this->application->parseContainerLabels();
if (base64_encode(base64_decode(data_get($this->application, 'custom_labels'), true)) === data_get($this->application, 'custom_labels')) { if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
ray('custom_labels is base64 encoded'); $this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
} else { $this->application->custom_labels = base64_encode($this->customLabels);
ray('custom_labels is not base64 encoded');
$this->application->custom_labels = str($this->application->custom_labels)->replace(',', "\n");
$this->application->custom_labels = base64_encode(data_get($this->application, 'custom_labels'));
$this->application->save(); $this->application->save();
} }
$this->customLabels = base64_decode(data_get($this->application, 'custom_labels'));
$this->initialDockerComposeLocation = $this->application->docker_compose_location; $this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->checkLabelUpdates(); $this->checkLabelUpdates();
} }
@@ -156,7 +157,6 @@ class General extends Component
$this->parsedServiceDomains[$serviceName]['domain'] = $domain; $this->parsedServiceDomains[$serviceName]['domain'] = $domain;
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->application->save(); $this->application->save();
$this->dispatch('success', 'Domain generated.');
} }
return $domain; return $domain;
} }
@@ -206,13 +206,20 @@ class General extends Component
public function submit($showToaster = true) public function submit($showToaster = true)
{ {
try { try {
if ($this->application->build_pack === 'dockercompose' && ($this->initialDockerComposeLocation !== $this->application->docker_compose_location || $this->initialDockerComposePrLocation !== $this->application->docker_compose_pr_location)) { if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save();
}
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
$this->loadComposeFile(); $this->loadComposeFile();
} }
$this->validate(); $this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes) { if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
} }
if (data_get($this->application, 'build_pack') === 'dockerimage') { if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([ $this->validate([
'application.docker_registry_image_name' => 'required', 'application.docker_registry_image_name' => 'required',
@@ -242,7 +249,6 @@ class General extends Component
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->parsedServices = $this->application->parseCompose(); $this->parsedServices = $this->application->parseCompose();
} }
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();
$showToaster && $this->dispatch('success', 'Application settings updated!'); $showToaster && $this->dispatch('success', 'Application settings updated!');

View File

@@ -60,12 +60,12 @@ class Heading extends Component
force_rebuild: false, force_rebuild: false,
is_new_deployment: true, is_new_deployment: true,
); );
return $this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} }
public function deploy(bool $force_rebuild = false) public function deploy(bool $force_rebuild = false)
{ {
@@ -73,18 +73,22 @@ class Heading extends Component
$this->dispatch('error', 'Please load a Compose file first.'); $this->dispatch('error', 'Please load a Compose file first.');
return; return;
} }
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'Please set a Docker image name first.');
return;
}
$this->setDeploymentUuid(); $this->setDeploymentUuid();
queue_application_deployment( queue_application_deployment(
application_id: $this->application->id, application_id: $this->application->id,
deployment_uuid: $this->deploymentUuid, deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild, force_rebuild: $force_rebuild,
); );
$this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} }
protected function setDeploymentUuid() protected function setDeploymentUuid()
@@ -109,12 +113,12 @@ class Heading extends Component
restart_only: true, restart_only: true,
is_new_deployment: true, is_new_deployment: true,
); );
return $this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} }
public function restart() public function restart()
{ {
@@ -124,11 +128,11 @@ class Heading extends Component
deployment_uuid: $this->deploymentUuid, deployment_uuid: $this->deploymentUuid,
restart_only: true, restart_only: true,
); );
return $this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid, 'deployment_uuid' => $this->deploymentUuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} }
} }

View File

@@ -52,12 +52,12 @@ class Previews extends Component
force_rebuild: true, force_rebuild: true,
pull_request_id: $pull_request_id, pull_request_id: $pull_request_id,
); );
return $this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deployment_uuid, 'deployment_uuid' => $this->deployment_uuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@@ -72,10 +72,14 @@ class Previews extends Component
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
{ {
try { try {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); if ($this->application->destination->server->isSwarm()) {
foreach ($containers as $container) { instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
$name = str_replace('/', '', $container['Names']); } else {
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); $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);
}
} }
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->application->refresh();

View File

@@ -29,12 +29,12 @@ class Rollback extends Component
commit: $commit, commit: $commit,
force_rebuild: false, force_rebuild: false,
); );
return $this->redirectRoute('project.application.deployment', [ return redirect()->route('project.application.deployment', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'], 'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $deployment_uuid, 'deployment_uuid' => $deployment_uuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
], navigate: true); ]);
} }
public function loadImages($showToast = false) public function loadImages($showToast = false)

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Livewire\Project\Application;
use App\Models\Application;
use Livewire\Component;
class Swarm extends Component
{
public Application $application;
public string $swarm_placement_constraints = '';
protected $rules = [
'application.swarm_replicas' => 'required',
'application.swarm_placement_constraints' => 'nullable',
'application.settings.is_swarm_only_worker_nodes' => 'required',
];
public function mount() {
if ($this->application->swarm_placement_constraints) {
$this->swarm_placement_constraints = base64_decode($this->application->swarm_placement_constraints);
}
}
public function instantSave() {
try {
$this->validate();
$this->application->settings->save();
$this->dispatch('success', 'Swarm settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit() {
try {
$this->validate();
if ($this->swarm_placement_constraints) {
$this->application->swarm_placement_constraints = base64_encode($this->swarm_placement_constraints);
} else {
$this->application->swarm_placement_constraints = null;
}
$this->application->save();
$this->dispatch('success', 'Swarm settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.application.swarm');
}
}

View File

@@ -19,12 +19,14 @@ class CloneProject extends Component
public $servers; public $servers;
public ?Environment $environment = null; public ?Environment $environment = null;
public ?int $selectedServer = null; public ?int $selectedServer = null;
public ?int $selectedDestination = null;
public ?Server $server = null; public ?Server $server = null;
public $resources = []; public $resources = [];
public string $newProjectName = ''; public string $newProjectName = '';
protected $messages = [ protected $messages = [
'selectedServer' => 'Please select a server.', 'selectedServer' => 'Please select a server.',
'selectedDestination' => 'Please select a server & destination.',
'newProjectName' => 'Please enter a name for the new project.', 'newProjectName' => 'Please enter a name for the new project.',
]; ];
public function mount($project_uuid) public function mount($project_uuid)
@@ -34,7 +36,7 @@ class CloneProject extends Component
$this->environment = $this->project->environments->where('name', $this->environment_name)->first(); $this->environment = $this->project->environments->where('name', $this->environment_name)->first();
$this->project_id = $this->project->id; $this->project_id = $this->project->id;
$this->servers = currentTeam()->servers; $this->servers = currentTeam()->servers;
$this->newProjectName = $this->project->name . ' (clone)'; $this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
} }
public function render() public function render()
@@ -42,9 +44,10 @@ class CloneProject extends Component
return view('livewire.project.clone-project'); return view('livewire.project.clone-project');
} }
public function selectServer($server_id) public function selectServer($server_id, $destination_id)
{ {
$this->selectedServer = $server_id; $this->selectedServer = $server_id;
$this->selectedDestination = $destination_id;
$this->server = $this->servers->where('id', $server_id)->first(); $this->server = $this->servers->where('id', $server_id)->first();
} }
@@ -52,7 +55,7 @@ class CloneProject extends Component
{ {
try { try {
$this->validate([ $this->validate([
'selectedServer' => 'required', 'selectedDestination' => 'required',
'newProjectName' => 'required', 'newProjectName' => 'required',
]); ]);
$foundProject = Project::where('name', $this->newProjectName)->first(); $foundProject = Project::where('name', $this->newProjectName)->first();
@@ -81,7 +84,8 @@ class CloneProject extends Component
'fqdn' => generateFqdn($this->server, $uuid), 'fqdn' => generateFqdn($this->server, $uuid),
'status' => 'exited', 'status' => 'exited',
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, // This is not correct, but we need to set it to something
'destination_id' => $this->selectedDestination,
]); ]);
$newApplication->save(); $newApplication->save();
$environmentVaribles = $application->environment_variables()->get(); $environmentVaribles = $application->environment_variables()->get();
@@ -107,7 +111,7 @@ class CloneProject extends Component
'status' => 'exited', 'status' => 'exited',
'started_at' => null, 'started_at' => null,
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedDestination,
]); ]);
$newDatabase->save(); $newDatabase->save();
$environmentVaribles = $database->environment_variables()->get(); $environmentVaribles = $database->environment_variables()->get();
@@ -133,7 +137,7 @@ class CloneProject extends Component
$newService = $service->replicate()->fill([ $newService = $service->replicate()->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'environment_id' => $newEnvironment->id, 'environment_id' => $newEnvironment->id,
'destination_id' => $this->selectedServer, 'destination_id' => $this->selectedDestination,
]); ]);
$newService->save(); $newService->save();
foreach ($newService->applications() as $application) { foreach ($newService->applications() as $application) {
@@ -148,10 +152,10 @@ class CloneProject extends Component
} }
$newService->parse(); $newService->parse();
} }
return $this->redirectRoute('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $newProject->uuid, 'project_uuid' => $newProject->uuid,
'environment_name' => $newEnvironment->name, 'environment_name' => $newEnvironment->name,
], navigate: true); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -50,9 +50,9 @@ class BackupEdit extends Component
$url = $url->withoutQueryParameter('selectedBackupId'); $url = $url->withoutQueryParameter('selectedBackupId');
$url = $url->withFragment('backups'); $url = $url->withFragment('backups');
$url = $url->getPath() . "#{$url->getFragment()}"; $url = $url->getPath() . "#{$url->getFragment()}";
return $this->redirect($url,navigate: true); return redirect($url);
} else { } else {
return $this->redirectRoute('project.database.backups.all', $this->parameters); return redirect()->route('project.database.backups.all', $this->parameters);
} }
} }

View File

@@ -8,7 +8,7 @@ use Livewire\Component;
class BackupExecutions extends Component class BackupExecutions extends Component
{ {
public $backup; public $backup;
public $executions; public $executions = [];
public $setDeletableBackup; public $setDeletableBackup;
public function getListeners() public function getListeners()
{ {
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
} }
public function refreshBackupExecutions(): void public function refreshBackupExecutions(): void
{ {
$this->executions = $this->backup->executions; $this->executions = data_get($this->backup, 'executions', []);
} }
} }

View File

@@ -23,7 +23,7 @@ class DeleteEnvironment extends Component
$environment = Environment::findOrFail($this->environment_id); $environment = Environment::findOrFail($this->environment_id);
if ($environment->isEmpty()) { if ($environment->isEmpty()) {
$environment->delete(); $environment->delete();
return $this->redirectRoute('project.show', ['project_uuid' => $this->parameters['project_uuid']], navigate: true); return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
} }
return $this->dispatch('error', 'Environment has defined resources, please delete them first.'); return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
} }

View File

@@ -25,6 +25,6 @@ class DeleteProject extends Component
return $this->dispatch('error', 'Project has resources defined, please delete them first.'); return $this->dispatch('error', 'Project has resources defined, please delete them first.');
} }
$project->delete(); $project->delete();
return $this->redirectRoute('projects', navigate: true); return redirect()->route('projects');
} }
} }

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Livewire\Project;
use App\Models\Application;
use App\Models\Project;
use Livewire\Component;
class EnvironmentEdit extends Component
{
public Project $project;
public Application $application;
public $environment;
public array $parameters;
protected $rules = [
'environment.name' => 'required|min:3|max:255',
'environment.description' => 'nullable|min:3|max:255',
];
public function mount() {
$this->parameters = get_route_parameters();
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
}
public function submit()
{
$this->validate();
try {
$this->environment->save();
return redirect()->route('project.environment.edit', ['project_uuid' => $this->project->uuid, 'environment_name' => $this->environment->name]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.project.environment-edit');
}
}

View File

@@ -69,12 +69,12 @@ class DockerCompose extends Component
$service->parse(isNew: true); $service->parse(isNew: true);
return $this->redirectRoute('project.service.configuration', [ return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid, 'service_uuid' => $service->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -64,11 +64,11 @@ class DockerImage extends Component
'name' => 'docker-image-' . $application->uuid, 'name' => 'docker-image-' . $application->uuid,
'fqdn' => $fqdn 'fqdn' => $fqdn
]); ]);
return $this->redirectRoute('project.application.configuration', [ return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
], navigate: true); ]);
} }
public function render() public function render()
{ {

View File

@@ -13,6 +13,6 @@ class EmptyProject extends Component
'name' => generate_random_name(), 'name' => generate_random_name(),
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
]); ]);
return $this->redirectRoute('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production'], navigate: true); return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
} }
} }

View File

@@ -151,11 +151,11 @@ class GithubPrivateRepository extends Component
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid); $application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
$application->save(); $application->save();
return $this->redirectRoute('project.application.configuration', [ return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -55,7 +55,11 @@ class GithubPrivateRepositoryDeployKey extends Component
} }
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->query = request()->query(); $this->query = request()->query();
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->where('id', '!=', 0)->get(); if (isDev()) {
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->get();
} else {
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->where('id', '!=', 0)->get();
}
} }
public function instantSave() public function instantSave()
@@ -132,11 +136,11 @@ class GithubPrivateRepositoryDeployKey extends Component
$application->name = generate_random_name($application->uuid); $application->name = generate_random_name($application->uuid);
$application->save(); $application->save();
return $this->redirectRoute('project.application.configuration', [ return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -184,11 +184,11 @@ class PublicGitRepository extends Component
$application->fqdn = $fqdn; $application->fqdn = $fqdn;
$application->save(); $application->save();
return $this->redirectRoute('project.application.configuration', [ return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -6,22 +6,24 @@ use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use Countable; use Countable;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Livewire\Component; use Livewire\Component;
class Select extends Component class Select extends Component
{ {
public $current_step = 'type'; public $current_step = 'type';
public ?int $server = null; public ?Server $server = null;
public string $type; public string $type;
public string $server_id; public string $server_id;
public string $destination_uuid; public string $destination_uuid;
public Countable|array|Server $allServers = [];
public Countable|array|Server $servers = []; public Countable|array|Server $servers = [];
public Collection|array $standaloneDockers = []; public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = []; public Collection|array $swarmDockers = [];
public array $parameters; public array $parameters;
public Collection|array $services = []; public Collection|array $services = [];
public Collection|array $allServices = []; public Collection|array $allServices = [];
public bool $isDatabase = false;
public bool $includeSwarm = true;
public bool $loadingServices = true; public bool $loadingServices = true;
public bool $loading = false; public bool $loading = false;
@@ -31,7 +33,7 @@ class Select extends Component
public ?string $search = null; public ?string $search = null;
protected $queryString = [ protected $queryString = [
'server', 'server_id',
'search' 'search'
]; ];
@@ -53,10 +55,10 @@ class Select extends Component
public function updatedSelectedEnvironment() public function updatedSelectedEnvironment()
{ {
return $this->redirectRoute('project.resources.new', [ return redirect()->route('project.resources.new', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->selectedEnvironment, 'environment_name' => $this->selectedEnvironment,
], navigate: true); ]);
} }
// public function addExistingPostgresql() // public function addExistingPostgresql()
@@ -97,21 +99,45 @@ class Select extends Component
$this->loadingServices = false; $this->loadingServices = false;
} }
} }
public function instantSave()
{
if ($this->includeSwarm) {
$this->servers = $this->allServers;
} else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
}
}
public function setType(string $type) public function setType(string $type)
{ {
$this->type = $type;
if ($this->loading) return; if ($this->loading) return;
$this->loading = true; $this->loading = true;
$this->type = $type;
switch ($type) {
case 'postgresql':
case 'mysql':
case 'mariadb':
case 'redis':
case 'mongodb':
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
break;
}
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
}
if ($type === "existing-postgresql") { if ($type === "existing-postgresql") {
$this->current_step = $type; $this->current_step = $type;
return; return;
} }
if (count($this->servers) === 1) { // if (count($this->servers) === 1) {
$server = $this->servers->first(); // $server = $this->servers->first();
$this->setServer($server); // $this->setServer($server);
} // }
if (!is_null($this->server)) { if (!is_null($this->server)) {
$foundServer = $this->servers->where('id', $this->server)->first(); $foundServer = $this->servers->where('id', $this->server->id)->first();
if ($foundServer) { if ($foundServer) {
return $this->setServer($foundServer); return $this->setServer($foundServer);
} }
@@ -122,6 +148,7 @@ class Select extends Component
public function setServer(Server $server) public function setServer(Server $server)
{ {
$this->server_id = $server->id; $this->server_id = $server->id;
$this->server = $server;
$this->standaloneDockers = $server->standaloneDockers; $this->standaloneDockers = $server->standaloneDockers;
$this->swarmDockers = $server->swarmDockers; $this->swarmDockers = $server->swarmDockers;
$this->current_step = 'destinations'; $this->current_step = 'destinations';
@@ -142,5 +169,6 @@ class Select extends Component
public function loadServers() public function loadServers()
{ {
$this->servers = Server::isUsable()->get(); $this->servers = Server::isUsable()->get();
$this->allServers = $this->servers;
} }
} }

View File

@@ -70,10 +70,10 @@ CMD ["nginx", "-g", "daemon off;"]
'fqdn' => $fqdn 'fqdn' => $fqdn
]); ]);
return $this->redirectRoute('project.application.configuration', [ return redirect()->route('project.application.configuration', [
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
'environment_name' => $environment->name, 'environment_name' => $environment->name,
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
], navigate: true); ]);
} }
} }

View File

@@ -41,7 +41,7 @@ class Application extends Component
try { try {
$this->application->delete(); $this->application->delete();
$this->dispatch('success', 'Application deleted successfully.'); $this->dispatch('success', 'Application deleted successfully.');
return $this->redirectRoute('project.service.configuration', $this->parameters, navigate: true); return redirect()->route('project.service.configuration', $this->parameters);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project\Service; namespace App\Livewire\Project\Service;
use App\Actions\Shared\PullImage;
use App\Actions\Service\StartService; use App\Actions\Service\StartService;
use App\Actions\Service\StopService; use App\Actions\Service\StopService;
use App\Events\ServiceStatusChanged; use App\Events\ServiceStatusChanged;
@@ -69,4 +70,18 @@ class Navbar extends Component
} }
ServiceStatusChanged::dispatch(); ServiceStatusChanged::dispatch();
} }
public function restart()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return;
}
PullImage::run($this->service);
$this->dispatch('image-pulled');
StopService::run($this->service);
$this->service->parse();
$activity = StartService::run($this->service);
$this->dispatch('newMonitorActivity', $activity->id);
}
} }

View File

@@ -25,10 +25,10 @@ class Danger extends Component
{ {
try { try {
DeleteResourceJob::dispatchSync($this->resource); DeleteResourceJob::dispatchSync($this->resource);
return $this->redirectRoute('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $this->projectUuid, 'project_uuid' => $this->projectUuid,
'environment_name' => $this->environmentName 'environment_name' => $this->environmentName
], navigate: true); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -65,7 +65,11 @@ class Show extends Component
public function delete() public function delete()
{ {
$this->env->delete(); try {
$this->dispatch('refreshEnvs'); $this->env->delete();
$this->dispatch('refreshEnvs');
} catch (\Exception $e) {
return handleError($e);
}
} }
} }

View File

@@ -73,9 +73,17 @@ class GetLogs extends Component
if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return; if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return;
if ($this->container) { if ($this->container) {
if ($this->showTimeStamps) { if ($this->showTimeStamps) {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}"); if ($this->server->isSwarm()) {
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} -t {$this->container}");
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
}
} else { } else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}"); if ($this->server->isSwarm()) {
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} {$this->container}");
} else {
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
}
} }
if ($refresh) { if ($refresh) {
$this->outputs = ''; $this->outputs = '';

View File

@@ -34,7 +34,15 @@ class Logs extends Component
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail(); $this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status; $this->status = $this->resource->status;
$this->server = $this->resource->destination->server; $this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0); if ($this->server->isSwarm()) {
$containers = collect([
[
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
]
]);
} else {
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
}
if ($containers->count() > 0) { if ($containers->count() > 0) {
$containers->each(function ($container) { $containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names'])); $this->containers->push(str_replace('/', '', $container['Names']));
@@ -62,7 +70,7 @@ class Logs extends Component
$this->status = $this->resource->status; $this->status = $this->resource->status;
$this->server = $this->resource->destination->server; $this->server = $this->resource->destination->server;
$this->container = $this->resource->uuid; $this->container = $this->resource->uuid;
if (str(data_get($this,'resource.status'))->startsWith('running')) { if (str(data_get($this, 'resource.status'))->startsWith('running')) {
$this->containers->push($this->container); $this->containers->push($this->container);
} }
} else if (data_get($this->parameters, 'service_uuid')) { } else if (data_get($this->parameters, 'service_uuid')) {

View File

@@ -2,22 +2,26 @@
namespace App\Livewire\Project\Shared\Storages; namespace App\Livewire\Project\Shared\Storages;
use App\Models\Application;
use Livewire\Component; use Livewire\Component;
class Add extends Component class Add extends Component
{ {
public $uuid; public $uuid;
public $parameters; public $parameters;
public $isSwarm = false;
public string $name; public string $name;
public string $mount_path; public string $mount_path;
public string|null $host_path = null; public ?string $host_path = null;
protected $listeners = ['clearAddStorage' => 'clear']; public $rules = [
protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'mount_path' => 'required|string', 'mount_path' => 'required|string',
'host_path' => 'string|nullable', 'host_path' => 'string|nullable',
]; ];
protected $listeners = ['clearAddStorage' => 'clear'];
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'name', 'name' => 'name',
'mount_path' => 'mount', 'mount_path' => 'mount',
@@ -27,17 +31,33 @@ class Add extends Component
public function mount() public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$applicationUuid = $this->parameters['application_uuid'];
$application = Application::where('uuid', $applicationUuid)->first();
if (!$application) {
abort(404);
}
if ($application->destination->server->isSwarm()) {
$this->isSwarm = true;
$this->rules['host_path'] = 'required|string';
}
}
} }
public function submit() public function submit()
{ {
$this->validate(); try {
$name = $this->uuid . '-' . $this->name; $this->validate($this->rules);
$this->dispatch('addNewVolume', [ $name = $this->uuid . '-' . $this->name;
'name' => $name, $this->dispatch('addNewVolume', [
'mount_path' => $this->mount_path, 'name' => $name,
'host_path' => $this->host_path, 'mount_path' => $this->mount_path,
]); 'host_path' => $this->host_path,
]);
$this->dispatch('closeStorageModal');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function clear() public function clear()

View File

@@ -19,7 +19,7 @@ class Delete extends Component
return; return;
} }
$this->server->delete(); $this->server->delete();
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -15,7 +15,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -25,7 +25,7 @@ class Form extends Component
'server.settings.is_cloudflare_tunnel' => 'required|boolean', 'server.settings.is_cloudflare_tunnel' => 'required|boolean',
'server.settings.is_reachable' => 'required', 'server.settings.is_reachable' => 'required',
'server.settings.is_swarm_manager' => 'required|boolean', 'server.settings.is_swarm_manager' => 'required|boolean',
// 'server.settings.is_swarm_worker' => 'required|boolean', 'server.settings.is_swarm_worker' => 'required|boolean',
'wildcard_domain' => 'nullable|url', 'wildcard_domain' => 'nullable|url',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -37,16 +37,13 @@ class Form extends Component
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel', 'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
'server.settings.is_reachable' => 'Is reachable', 'server.settings.is_reachable' => 'Is reachable',
'server.settings.is_swarm_manager' => 'Swarm Manager', 'server.settings.is_swarm_manager' => 'Swarm Manager',
// 'server.settings.is_swarm_worker' => 'Swarm Worker', 'server.settings.is_swarm_worker' => 'Swarm Worker',
]; ];
public function mount() public function mount()
{ {
$this->wildcard_domain = $this->server->settings->wildcard_domain; $this->wildcard_domain = $this->server->settings->wildcard_domain;
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
if (!$this->server->isFunctional()) {
$this->validateServer();
}
} }
public function serverRefresh($install = true) public function serverRefresh($install = true)
{ {

View File

@@ -43,7 +43,7 @@ class LogDrains extends Component
try { try {
$server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($server)) { if (is_null($server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
$this->server = $server; $this->server = $server;
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -22,7 +22,10 @@ class ByIp extends Component
public string $user = 'root'; public string $user = 'root';
public int $port = 22; public int $port = 22;
public bool $is_swarm_manager = false; public bool $is_swarm_manager = false;
public bool $is_swarm_worker = false;
public $selected_swarm_cluster = null;
public $swarm_managers = [];
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'description' => 'nullable|string', 'description' => 'nullable|string',
@@ -30,6 +33,7 @@ class ByIp extends Component
'user' => 'required|string', 'user' => 'required|string',
'port' => 'required|integer', 'port' => 'required|integer',
'is_swarm_manager' => 'required|boolean', 'is_swarm_manager' => 'required|boolean',
'is_swarm_worker' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'Name', 'name' => 'Name',
@@ -38,12 +42,17 @@ class ByIp extends Component
'user' => 'User', 'user' => 'User',
'port' => 'Port', 'port' => 'Port',
'is_swarm_manager' => 'Swarm Manager', 'is_swarm_manager' => 'Swarm Manager',
'is_swarm_worker' => 'Swarm Worker',
]; ];
public function mount() public function mount()
{ {
$this->name = generate_random_name(); $this->name = generate_random_name();
$this->private_key_id = $this->private_keys->first()->id; $this->private_key_id = $this->private_keys->first()->id;
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
if ($this->swarm_managers->count() > 0) {
$this->selected_swarm_cluster = $this->swarm_managers->first()->id;
}
} }
public function setPrivateKey(string $private_key_id) public function setPrivateKey(string $private_key_id)
@@ -53,7 +62,7 @@ class ByIp extends Component
public function instantSave() public function instantSave()
{ {
$this->dispatch('success', 'Application settings updated!'); // $this->dispatch('success', 'Application settings updated!');
} }
public function submit() public function submit()
@@ -63,7 +72,7 @@ class ByIp extends Component
if (is_null($this->private_key_id)) { if (is_null($this->private_key_id)) {
return $this->dispatch('error', 'You must select a private key'); return $this->dispatch('error', 'You must select a private key');
} }
$server = Server::create([ $payload = [
'name' => $this->name, 'name' => $this->name,
'description' => $this->description, 'description' => $this->description,
'ip' => $this->ip, 'ip' => $this->ip,
@@ -75,11 +84,16 @@ class ByIp extends Component
"type" => ProxyTypes::TRAEFIK_V2->value, "type" => ProxyTypes::TRAEFIK_V2->value,
"status" => ProxyStatus::EXITED->value, "status" => ProxyStatus::EXITED->value,
], ],
]); ];
if ($this->is_swarm_worker) {
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
}
$server = Server::create($payload);
$server->settings->is_swarm_manager = $this->is_swarm_manager; $server->settings->is_swarm_manager = $this->is_swarm_manager;
$server->settings->is_swarm_worker = $this->is_swarm_worker;
$server->settings->save(); $server->settings->save();
$server->addInitialNetwork(); $server->addInitialNetwork();
return $this->redirectRoute('server.show', $server->uuid, navigate: true); return redirect()->route('server.show', $server->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -17,7 +17,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false); $this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -15,7 +15,7 @@ class Logs extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -20,7 +20,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@@ -15,7 +15,6 @@ class Status extends Component
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling']; protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
public function mount() { public function mount() {
$this->checkProxy();
} }
public function startProxyPolling() public function startProxyPolling()
{ {

View File

@@ -17,7 +17,7 @@ class Show extends Component
try { try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) { if (is_null($this->server)) {
return $this->redirectRoute('server.all', navigate: true); return redirect()->route('server.all');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -106,7 +106,7 @@ class Email extends Component
public function sendTestNotification() public function sendTestNotification()
{ {
$this->settings->notify(new Test($this->emails)); $this->settings?->notify(new Test($this->emails));
$this->dispatch('success', 'Test email sent.'); $this->dispatch('success', 'Test email sent.');
} }
} }

View File

@@ -41,7 +41,7 @@ class Change extends Component
$github_app_uuid = request()->github_app_uuid; $github_app_uuid = request()->github_app_uuid;
$this->github_app = GithubApp::where('uuid', $github_app_uuid)->first(); $this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
if (!$this->github_app) { if (!$this->github_app) {
return $this->redirectRoute('source.all', navigate: true); return redirect()->route('source.all');
} }
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
@@ -67,7 +67,7 @@ class Change extends Component
$type = data_get($parameters, 'type'); $type = data_get($parameters, 'type');
$destination = data_get($parameters, 'destination'); $destination = data_get($parameters, 'destination');
session()->forget('from'); session()->forget('from');
return $this->redirectRoute($back, [ return redirect()->route($back, [
'environment_name' => $environment_name, 'environment_name' => $environment_name,
'project_uuid' => $project_uuid, 'project_uuid' => $project_uuid,
'type' => $type, 'type' => $type,
@@ -117,7 +117,7 @@ class Change extends Component
{ {
try { try {
$this->github_app->delete(); $this->github_app->delete();
return $this->redirectRoute('source.all', navigate: true); return redirect()->route('source.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -48,7 +48,7 @@ class Create extends Component
if (session('from')) { if (session('from')) {
session(['from' => session('from') + ['source_id' => $github_app->id]]); session(['from' => session('from') + ['source_id' => $github_app->id]]);
} }
return $this->redirectRoute('source.github.show', ['github_app_uuid' => $github_app->uuid], navigate: true); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Subscription; namespace App\Livewire\Subscription;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Providers\RouteServiceProvider;
use Livewire\Component; use Livewire\Component;
class Show extends Component class Show extends Component
@@ -11,7 +12,7 @@ class Show extends Component
public bool $alreadySubscribed = false; public bool $alreadySubscribed = false;
public function mount() { public function mount() {
if (!isCloud()) { if (!isCloud()) {
return $this->redirect('/', navigate: true); return redirect(RouteServiceProvider::HOME);
} }
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
$this->alreadySubscribed = currentTeam()->subscription()->exists(); $this->alreadySubscribed = currentTeam()->subscription()->exists();

View File

@@ -30,7 +30,7 @@ class Create extends Component
]); ]);
auth()->user()->teams()->attach($team, ['role' => 'admin']); auth()->user()->teams()->attach($team, ['role' => 'admin']);
refreshSession(); refreshSession();
return $this->redirectRoute('team.index', navigate: true); return redirect()->route('team.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -24,6 +24,6 @@ class Delete extends Component
}); });
refreshSession(); refreshSession();
return $this->redirectRoute('team.index', navigate: true); return redirect()->route('team.index');
} }
} }

View File

@@ -65,7 +65,7 @@ class Create extends Component
$this->storage->team_id = currentTeam()->id; $this->storage->team_id = currentTeam()->id;
$this->storage->testConnection(); $this->storage->testConnection();
$this->storage->save(); $this->storage->save();
return $this->redirectRoute('team.storages.show', $this->storage->uuid, navigate: true); return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -43,7 +43,7 @@ class Form extends Component
{ {
try { try {
$this->storage->delete(); $this->storage->delete();
return $this->redirectRoute('team.storages.all', navigate: true); return redirect()->route('team.storages.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -24,7 +24,7 @@ class Index extends Component
public function mount() public function mount()
{ {
if (config('coolify.waitlist') == false) { if (config('coolify.waitlist') == false) {
return $this->redirectRoute('register', navigate: true); return redirect()->route('register');
} }
$this->waitingInLine = Waitlist::whereVerified(true)->count(); $this->waitingInLine = Waitlist::whereVerified(true)->count();
$this->users = User::count(); $this->users = User::count();

View File

@@ -2,8 +2,10 @@
namespace App\Models; namespace App\Models;
use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -13,6 +15,7 @@ use Visus\Cuid2\Cuid2;
class Application extends BaseModel class Application extends BaseModel
{ {
use SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()
@@ -338,7 +341,7 @@ class Application extends BaseModel
} }
public function isDeploymentInprogress() public function isDeploymentInprogress()
{ {
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'in_progress')->count(); $deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', ApplicationDeploymentStatus::QUEUED)->count();
if ($deployments > 0) { if ($deployments > 0) {
return true; return true;
} }
@@ -379,6 +382,9 @@ class Application extends BaseModel
public function deploymentType() public function deploymentType()
{ {
if (isDev() && data_get($this, 'private_key_id') === 0) {
return 'deploy_key';
}
if (data_get($this, 'private_key_id')) { if (data_get($this, 'private_key_id')) {
return 'deploy_key'; return 'deploy_key';
} else if (data_get($this, 'source')) { } else if (data_get($this, 'source')) {
@@ -630,7 +636,7 @@ class Application extends BaseModel
'memswap_limit' => $this->limits_memory_swap, 'memswap_limit' => $this->limits_memory_swap,
'mem_swappiness' => $this->limits_memory_swappiness, 'mem_swappiness' => $this->limits_memory_swappiness,
'mem_reservation' => $this->limits_memory_reservation, 'mem_reservation' => $this->limits_memory_reservation,
'cpus' => (int) $this->limits_cpus, 'cpus' => (float) $this->limits_cpus,
'cpuset' => $this->limits_cpuset, 'cpuset' => $this->limits_cpuset,
'cpu_shares' => $this->limits_cpu_shares, 'cpu_shares' => $this->limits_cpu_shares,
] ]
@@ -856,9 +862,12 @@ class Application extends BaseModel
} }
$private_key = base64_encode($private_key); $private_key = base64_encode($private_key);
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$customRepository} {$baseDir}"; $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$customRepository} {$baseDir}";
if (!$only_checkout) { if ($only_checkout) {
$git_clone_command = $git_clone_command_base;
} else {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base); $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
} }
ray($git_clone_command);
if ($exec_in_docker) { if ($exec_in_docker) {
$commands = collect([ $commands = collect([
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
@@ -963,65 +972,99 @@ class Application extends BaseModel
function loadComposeFile($isInit = false) function loadComposeFile($isInit = false)
{ {
$initialDockerComposeLocation = $this->docker_compose_location; $initialDockerComposeLocation = $this->docker_compose_location;
// $initialDockerComposePrLocation = $this->docker_compose_pr_location; if ($isInit && $this->docker_compose_raw) {
if ($this->build_pack === 'dockercompose') { return;
if ($isInit && $this->docker_compose_raw) {
return;
}
$uuid = new Cuid2();
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
$workdir = rtrim($this->base_directory, '/');
$composeFile = $this->docker_compose_location;
// $prComposeFile = $this->docker_compose_pr_location;
$fileList = collect([".$workdir$composeFile"]);
// if ($composeFile !== $prComposeFile) {
// $fileList->push(".$prComposeFile");
// }
$commands = collect([
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
$cloneCommand,
"git sparse-checkout init --cone",
"git sparse-checkout set {$fileList->implode(' ')}",
"git read-tree -mu HEAD",
"cat .$workdir$composeFile",
]);
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \Exception("Could not load base compose file from $workdir$composeFile");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
}
// if ($composeFile === $prComposeFile) {
// $this->docker_compose_pr_raw = $composeFileContent;
// $this->save();
// } else {
// $commands = collect([
// "cd /tmp/{$uuid}",
// "cat .$workdir$prComposeFile",
// ]);
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
// if (!$composePrFileContent) {
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
// $this->save();
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
// } else {
// $this->docker_compose_pr_raw = $composePrFileContent;
// $this->save();
// }
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
return [
'parsedServices' => $this->parseCompose(),
'initialDockerComposeLocation' => $this->docker_compose_location,
'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
];
} }
$uuid = new Cuid2();
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
$workdir = rtrim($this->base_directory, '/');
$composeFile = $this->docker_compose_location;
// $prComposeFile = $this->docker_compose_pr_location;
$fileList = collect([".$workdir$composeFile"]);
// if ($composeFile !== $prComposeFile) {
// $fileList->push(".$prComposeFile");
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
$cloneCommand,
"git sparse-checkout init --cone",
"git sparse-checkout set {$fileList->implode(' ')}",
"git read-tree -mu HEAD",
"cat .$workdir$composeFile",
]);
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
throw new \Exception("Could not load base compose file from $workdir$composeFile");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
}
// if ($composeFile === $prComposeFile) {
// $this->docker_compose_pr_raw = $composeFileContent;
// $this->save();
// } else {
// $commands = collect([
// "cd /tmp/{$uuid}",
// "cat .$workdir$prComposeFile",
// ]);
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
// if (!$composePrFileContent) {
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
// $this->save();
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
// } else {
// $this->docker_compose_pr_raw = $composePrFileContent;
// $this->save();
// }
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
$parsedServices = $this->parseCompose();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
$jsonNames = $json->keys()->toArray();
$diff = array_diff($jsonNames, $names);
$json = $json->filter(function ($value, $key) use ($diff) {
return !in_array($key, $diff);
});
if ($json) {
$this->docker_compose_domains = json_encode($json);
} else {
$this->docker_compose_domains = null;
}
$this->save();
}
return [
'parsedServices' => $parsedServices,
'initialDockerComposeLocation' => $this->docker_compose_location,
'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
];
}
function parseContainerLabels(?ApplicationPreview $preview = null)
{
$customLabels = data_get($this, 'custom_labels');
if (!$customLabels) {
return;
}
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
ray('custom_labels is not base64 encoded');
$this->custom_labels = str($customLabels)->replace(',', "\n");
$this->custom_labels = base64_encode($customLabels);
}
$customLabels = base64_decode($this->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
ray('custom_labels contains non-ascii characters');
$customLabels = str(implode(",", generateLabelsApplication($this, $preview)))->replace(',', "\n");
}
$this->custom_labels = base64_encode($customLabels);
$this->save();
return $customLabels;
} }
} }

View File

@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Illuminate\Support\Sleep;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait; use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -72,7 +71,7 @@ class Server extends BaseModel
static public function isUsable() static public function isUsable()
{ {
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true); return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false);
} }
static public function destinationsByServer(string $server_id) static public function destinationsByServer(string $server_id)
@@ -150,29 +149,45 @@ class Server extends BaseModel
} }
return false; return false;
} }
public function isServerReady() public function isServerReady(int $tries = 3)
{ {
if ($this->skipServer()) {
return false;
}
$serverUptimeCheckNumber = $this->unreachable_count; $serverUptimeCheckNumber = $this->unreachable_count;
$serverUptimeCheckNumberMax = 3; if ($this->unreachable_count < $tries) {
$serverUptimeCheckNumber = $this->unreachable_count + 1;
}
if ($this->unreachable_count > $tries) {
$serverUptimeCheckNumber = $tries;
}
$currentTime = now()->timestamp; $serverUptimeCheckNumberMax = $tries;
$runtime = 30;
$isReady = false; // ray('server: ' . $this->name);
// Run for 30 seconds max and check every 5 seconds for 3 times // ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
while ($currentTime + $runtime > now()->timestamp) { // ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
$result = $this->validateConnection();
if ($result) {
if ($this->unreachable_notification_sent === true) {
$this->update(['unreachable_notification_sent' => false]);
}
return true;
} else {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
// Reached max number of retries
if ($this->unreachable_notification_sent === false) { if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...'); ray('Server unreachable, sending notification...');
$this->team->notify(new Unreachable($this)); $this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]); $this->update(['unreachable_notification_sent' => true]);
} }
$this->settings()->update([ if ($this->settings->is_reachable === true) {
'is_reachable' => false, $this->settings()->update([
]); 'is_reachable' => false,
$this->update([ ]);
'unreachable_count' => 0, }
]);
foreach ($this->applications() as $application) { foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']); $application->update(['status' => 'exited']);
} }
@@ -189,23 +204,13 @@ class Server extends BaseModel
$db->update(['status' => 'exited']); $db->update(['status' => 'exited']);
} }
} }
$isReady = false; } else {
break;
}
$result = $this->validateConnection();
// ray('validateConnection: ' . $result);
if (!$result) {
$serverUptimeCheckNumber++;
$this->update([ $this->update([
'unreachable_count' => $serverUptimeCheckNumber, 'unreachable_count' => $this->unreachable_count + 1,
]); ]);
Sleep::for(5)->seconds();
return;
} }
$isReady = true; return false;
break;
} }
return $isReady;
} }
public function getDiskUsage() public function getDiskUsage()
{ {
@@ -379,9 +384,20 @@ class Server extends BaseModel
{ {
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker'); return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
} }
public function isSwarmManager()
{
return data_get($this, 'settings.is_swarm_manager');
}
public function isSwarmWorker()
{
return data_get($this, 'settings.is_swarm_worker');
}
public function validateConnection() public function validateConnection()
{ {
$server = Server::find($this->id); $server = Server::find($this->id);
if (!$server) {
return false;
}
if ($server->skipServer()) { if ($server->skipServer()) {
return false; return false;
} }
@@ -401,7 +417,7 @@ class Server extends BaseModel
} }
if (data_get($server, 'unreachable_notification_sent') === true) { if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team->notify(new Revived($server)); $server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]); $server->update(['unreachable_notification_sent' => false]);
} }

View File

@@ -4,11 +4,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class ServiceApplication extends BaseModel class ServiceApplication extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -3,10 +3,11 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
class ServiceDatabase extends BaseModel class ServiceDatabase extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMariadb extends BaseModel class StandaloneMariadb extends BaseModel
{ {
use HasFactory; use HasFactory,SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMongodb extends BaseModel class StandaloneMongodb extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneMysql extends BaseModel class StandaloneMysql extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandalonePostgresql extends BaseModel class StandalonePostgresql extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected $casts = [ protected $casts = [

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class StandaloneRedis extends BaseModel class StandaloneRedis extends BaseModel
{ {
use HasFactory; use HasFactory, SoftDeletes;
protected $guarded = []; protected $guarded = [];
protected static function booted() protected static function booted()

View File

@@ -98,7 +98,7 @@ class User extends Authenticatable implements SendsEmail
} }
public function sendPasswordResetNotification($token): void public function sendPasswordResetNotification($token): void
{ {
$this->notify(new TransactionalEmailsResetPassword($token)); $this?->notify(new TransactionalEmailsResetPassword($token));
} }
public function isAdmin() public function isAdmin()

View File

@@ -54,7 +54,7 @@ class Revived extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"; $message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array

View File

@@ -43,7 +43,7 @@ class Unreachable extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 3 times"); $mail->subject("Coolify: Your server ({$this->server->name}) is unreachable.");
$mail->view('emails.server-lost-connection', [ $mail->view('emails.server-lost-connection', [
'name' => $this->server->name, 'name' => $this->server->name,
]); ]);
@@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
public function toDiscord(): string public function toDiscord(): string
{ {
$message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."; $message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
return [ return [
"message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations." "message" => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
]; ];
} }
} }

View File

@@ -31,9 +31,9 @@ class FortifyServiceProvider extends ServiceProvider
{ {
// First user (root) will be redirected to /settings instead of / on registration. // First user (root) will be redirected to /settings instead of / on registration.
if ($request->user()->currentTeam->id === 0) { if ($request->user()->currentTeam->id === 0) {
return redirect('/settings'); return redirect()->route('settings.configuration');
} }
return redirect('/'); return redirect(RouteServiceProvider::HOME);
} }
}); });
} }

View File

@@ -41,11 +41,11 @@ function queue_application_deployment(int $application_id, string $deployment_uu
dispatch(new ApplicationDeploymentNewJob( dispatch(new ApplicationDeploymentNewJob(
deployment: $deployment, deployment: $deployment,
application: Application::find($application_id) application: Application::find($application_id)
))->onConnection('long-running')->onQueue('long-running'); ));
} else { } else {
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id, application_deployment_queue_id: $deployment->id,
))->onConnection('long-running')->onQueue('long-running'); ));
} }
} }
@@ -57,11 +57,11 @@ function queue_next_deployment(Application $application, bool $isNew = false)
dispatch(new ApplicationDeploymentNewJob( dispatch(new ApplicationDeploymentNewJob(
deployment: $next_found, deployment: $next_found,
application: $application application: $application
))->onConnection('long-running')->onQueue('long-running'); ));
} else { } else {
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id, application_deployment_queue_id: $next_found->id,
))->onConnection('long-running')->onQueue('long-running'); ));
} }
} }
} }

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