From 79a169f53e3bc5164292e17bc159737293207812 Mon Sep 17 00:00:00 2001 From: Telokis <6382729+Telokis@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:18:11 +0200 Subject: [PATCH 01/15] Add initial docker-compose.yaml for Pterodactyl --- public/svgs/pterodactyl.png | Bin 0 -> 32004 bytes templates/compose/pterodactyl.yaml | 157 +++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 public/svgs/pterodactyl.png create mode 100644 templates/compose/pterodactyl.yaml diff --git a/public/svgs/pterodactyl.png b/public/svgs/pterodactyl.png new file mode 100644 index 0000000000000000000000000000000000000000..a5addb87c5cf70b426a5a13a563a677088cad3a3 GIT binary patch literal 32004 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

MZBCd6fS^X@TN}n= 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 Ke1)t<@c#oi$r%~| literal 0 HcmV?d00001 diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml new file mode 100644 index 000000000..436065a40 --- /dev/null +++ b/templates/compose/pterodactyl.yaml @@ -0,0 +1,157 @@ +# documentation: https://pterodactyl.io/ +# slogan: Pterodactyl is a free, open-source game server management panel +# tags: game, game server, management, panel, minecraft +# logo: svgs/pterodactyl.png +# port: 80 + +services: + mariadb: + image: mariadb:10.5 + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password + healthcheck: + test: + ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized || exit 1"] + start_period: 10s + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_PASSWORD_MYSQL + - MYSQL_ROOT_PASSWORD=$SERVICE_PASSWORD_MYSQLROOT + - MYSQL_DATABASE=panel + - MYSQL_USER=pterodactyl + - MYSQL_PASSWORD=$SERVICE_PASSWORD_MYSQL + volumes: + - pterodactyl-db:/var/lib/mysql + + redis: + image: redis:alpine + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "redis-cli ping || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + + pterodactyl: + image: ghcr.io/pterodactyl/panel:latest + restart: unless-stopped + volumes: + - "panel-var:/app/var/" + - "panel-nginx:/etc/nginx/http.d/" + - "panel-certs:/etc/letsencrypt/" + - "panel-logs:/app/storage/logs" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] + interval: 10s + timeout: 1s + retries: 3 + environment: + - SERVICE_FQDN_PTERODACTYL + + - APP_ENV=production + - APP_ENVIRONMENT_ONLY=false + - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} + - APP_TIMEZONE=${TIMEZONE:-UTC} + - APP_SERVICE_AUTHOR=$APP_SERVICE_AUTHOR + - LOG_LEVEL=${LOG_LEVEL:-debug} + + - CACHE_DRIVER=redis + - SESSION_DRIVER=redis + - QUEUE_DRIVER=redis + - REDIS_HOST=redis + + - DB_HOST=mariadb + - DB_PORT=3306 + - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL + + - MAIL_FROM=$MAIL_FROM + - MAIL_DRIVER=$MAIL_DRIVER + - MAIL_HOST=$MAIL_HOST + - MAIL_PORT=$MAIL_PORT + - MAIL_USERNAME=$MAIL_USERNAME + - MAIL_PASSWORD=$MAIL_PASSWORD + - MAIL_ENCRYPTION=$MAIL_ENCRYPTION + + wings: + image: ghcr.io/pterodactyl/wings:latest + restart: unless-stopped + environment: + - TZ=${TIMEZONE:-UTC} + - WINGS_USERNAME=pterodactyl + healthcheck: + test: ["CMD", "curl", "-sf http://localhost:8080"] + interval: 10s + timeout: 1s + retries: 3 + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + - "/var/lib/docker/containers/:/var/lib/docker/containers/" + - "wings-lib:/var/lib/pterodactyl/" + - "wings-logs:/var/log/pterodactyl/" + + - type: bind + source: ./etc/config.yml + target: /etc/pterodactyl/config.yml + content: | + docker: + network: + interface: 172.28.0.1 + dns: + - 1.1.1.1 + - 1.0.0.1 + name: pterodactyl_nw + ispn: false + driver: "" + network_mode: pterodactyl_nw + is_internal: false + enable_icc: true + network_mtu: 1500 + interfaces: + v4: + subnet: 172.28.0.0/16 + gateway: 172.28.0.1 + v6: + subnet: fdba:17c8:6c94::/64 + gateway: fdba:17c8:6c94::1011 + +volumes: + panel-var: + panel-nginx: + panel-certs: + panel-logs: + wings-lib: + wings-logs: + pterodactyl-db: +# Instructions: +# - Wait for the Pterodactyl service to be healthy (can take a few minutes) +# - Use the command `php artisan p:user:make --no-interaction --admin=1 --email= --username= --name-first= --name-last= --password=` in the Pterodactyl container to create an admin user. +# - Login to the panel using the created user +# - Go to the Admin panel (Cog icon at the top) +# - Go to "Locations" on the left +# - Create a new location +# - Go to "Nodes" on the left +# - Create a new node +# - Specify a temporary FQDN like "localhost" +# - /!\ Check "Use HTTP Connection" /!\ +# - Go to the "Configuration" tab of your newly created node +# - Copy the configuration file +# - In Coolify go to the "Storage" menu for your resource. +# - Find the big text area associated with `config.yml` for Wings +# - Paste the configuration at the top of this file, above `docker:` +# - Edit the line `remote: ''` to `remote: 'http://pterodactyl'` +# - Save the file +# - On the Pterodactyl panel for the node, go to the "Settings" tab +# - Change the "Fully Qualified Domain Name" from `localhost` to `wings` +# - Go to the "About" tab and confirm that the "Information" section shows the "Daemon Version" properly. + +# +----------+--------------------------------------+ +# | Field | Value | +# +----------+--------------------------------------+ +# | UUID | 6b3083ca-274b-4a77-b88f-6fbf5e4f286f | +# | Email | telokis@example.com | +# | Username | telokis | +# | Name | Telo Kis | +# | Admin | Yes | +# +----------+--------------------------------------+ From de6be8c840f7737a6f51e8cd5f620f0e6c47fecc Mon Sep 17 00:00:00 2001 From: Telokis <6382729+Telokis@users.noreply.github.com> Date: Sun, 11 Aug 2024 21:06:24 +0200 Subject: [PATCH 02/15] Fix a lot of small mistakes --- templates/compose/pterodactyl.yaml | 45 +++--------------------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml index 436065a40..19315b4ce 100644 --- a/templates/compose/pterodactyl.yaml +++ b/templates/compose/pterodactyl.yaml @@ -41,7 +41,6 @@ services: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" - "panel-certs:/etc/letsencrypt/" - - "panel-logs:/app/storage/logs" healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] interval: 10s @@ -54,7 +53,7 @@ services: - APP_ENVIRONMENT_ONLY=false - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} - APP_TIMEZONE=${TIMEZONE:-UTC} - - APP_SERVICE_AUTHOR=$APP_SERVICE_AUTHOR + - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} - LOG_LEVEL=${LOG_LEVEL:-debug} - CACHE_DRIVER=redis @@ -78,17 +77,14 @@ services: image: ghcr.io/pterodactyl/wings:latest restart: unless-stopped environment: + - SERVICE_FQDN_WINGS_8080 - TZ=${TIMEZONE:-UTC} - WINGS_USERNAME=pterodactyl - healthcheck: - test: ["CMD", "curl", "-sf http://localhost:8080"] - interval: 10s - timeout: 1s - retries: 3 volumes: - "/var/run/docker.sock:/var/run/docker.sock" - "/var/lib/docker/containers/:/var/lib/docker/containers/" - - "wings-lib:/var/lib/pterodactyl/" + - "/var/lib/pterodactyl/:/var/lib/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 + - "/tmp/pterodactyl/:/tmp/pterodactyl/" # See https://discord.com/channels/122900397965705216/493443725012500490/1272195151309045902 - "wings-logs:/var/log/pterodactyl/" - type: bind @@ -120,38 +116,5 @@ volumes: panel-var: panel-nginx: panel-certs: - panel-logs: - wings-lib: wings-logs: pterodactyl-db: -# Instructions: -# - Wait for the Pterodactyl service to be healthy (can take a few minutes) -# - Use the command `php artisan p:user:make --no-interaction --admin=1 --email= --username= --name-first= --name-last= --password=` in the Pterodactyl container to create an admin user. -# - Login to the panel using the created user -# - Go to the Admin panel (Cog icon at the top) -# - Go to "Locations" on the left -# - Create a new location -# - Go to "Nodes" on the left -# - Create a new node -# - Specify a temporary FQDN like "localhost" -# - /!\ Check "Use HTTP Connection" /!\ -# - Go to the "Configuration" tab of your newly created node -# - Copy the configuration file -# - In Coolify go to the "Storage" menu for your resource. -# - Find the big text area associated with `config.yml` for Wings -# - Paste the configuration at the top of this file, above `docker:` -# - Edit the line `remote: ''` to `remote: 'http://pterodactyl'` -# - Save the file -# - On the Pterodactyl panel for the node, go to the "Settings" tab -# - Change the "Fully Qualified Domain Name" from `localhost` to `wings` -# - Go to the "About" tab and confirm that the "Information" section shows the "Daemon Version" properly. - -# +----------+--------------------------------------+ -# | Field | Value | -# +----------+--------------------------------------+ -# | UUID | 6b3083ca-274b-4a77-b88f-6fbf5e4f286f | -# | Email | telokis@example.com | -# | Username | telokis | -# | Name | Telo Kis | -# | Admin | Yes | -# +----------+--------------------------------------+ From 3c5c0fddbaf22faa1fce98def1ffc4bb2fe2ed40 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:06:25 +0100 Subject: [PATCH 03/15] automate some things for Pterodactyl --- templates/compose/pterodactyl.yaml | 49 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/templates/compose/pterodactyl.yaml b/templates/compose/pterodactyl.yaml index 19315b4ce..ea64de47a 100644 --- a/templates/compose/pterodactyl.yaml +++ b/templates/compose/pterodactyl.yaml @@ -1,3 +1,4 @@ +# ignore: true # documentation: https://pterodactyl.io/ # slogan: Pterodactyl is a free, open-source game server management panel # tags: game, game server, management, panel, minecraft @@ -41,6 +42,37 @@ services: - "panel-var:/app/var/" - "panel-nginx:/etc/nginx/http.d/" - "panel-certs:/etc/letsencrypt/" + - type: bind + source: ./etc/entrypoint.sh + target: /entrypoint.sh + mode: "0755" + content: | + #!/bin/sh + set -e + + echo "Waiting for services to be ready..." + sleep 30 + + echo "Setting logs permissions..." + chown -R nginx: /app/storage/logs/ + + if ! php artisan p:user:list | grep -q "$ADMIN_EMAIL"; then + echo "Creating admin user..." + php artisan p:user:make --no-interaction \ + --admin=1 \ + --email="$ADMIN_EMAIL" \ + --username="$ADMIN_USERNAME" \ + --name-first="$ADMIN_FIRSTNAME" \ + --name-last="$ADMIN_LASTNAME" \ + --password="$ADMIN_PASSWORD" + echo "Admin user created" + else + echo "Admin user already exists, skipping creation" + fi + + exec supervisord -c --nodaemon + + command: ["/entrypoint.sh"] healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"] interval: 10s @@ -48,23 +80,25 @@ services: retries: 3 environment: - SERVICE_FQDN_PTERODACTYL - + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} + - ADMIN_USERNAME=${SERVICE_USER_ADMIN} + - ADMIN_FIRSTNAME=${ADMIN_FIRSTNAME:-Admin} + - ADMIN_LASTNAME=${ADMIN_LASTNAME:-User} + - ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - PTERODACTYL_HTTPS=${PTERODACTYL_HTTPS:-false} - APP_ENV=production - APP_ENVIRONMENT_ONLY=false - APP_URL=${PTERODACTYL_PUBLIC_FQDN:-$SERVICE_FQDN_PTERODACTYL} - APP_TIMEZONE=${TIMEZONE:-UTC} - APP_SERVICE_AUTHOR=${APP_SERVICE_AUTHOR:-author@example.com} - LOG_LEVEL=${LOG_LEVEL:-debug} - - CACHE_DRIVER=redis - SESSION_DRIVER=redis - QUEUE_DRIVER=redis - REDIS_HOST=redis - - DB_HOST=mariadb - DB_PORT=3306 - DB_PASSWORD=$SERVICE_PASSWORD_MYSQL - - MAIL_FROM=$MAIL_FROM - MAIL_DRIVER=$MAIL_DRIVER - MAIL_HOST=$MAIL_HOST @@ -111,10 +145,3 @@ services: v6: subnet: fdba:17c8:6c94::/64 gateway: fdba:17c8:6c94::1011 - -volumes: - panel-var: - panel-nginx: - panel-certs: - wings-logs: - pterodactyl-db: From b08f32508c1919d8f7da89522dc325ffcf427048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Tremblay=20Lefran=C3=A7ois?= Date: Mon, 2 Dec 2024 13:14:00 -0500 Subject: [PATCH 04/15] added sonarr --- public/svgs/sonarr.svg | 1 + templates/compose/sonarr.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 public/svgs/sonarr.svg create mode 100644 templates/compose/sonarr.yaml diff --git a/public/svgs/sonarr.svg b/public/svgs/sonarr.svg new file mode 100644 index 000000000..91c04e289 --- /dev/null +++ b/public/svgs/sonarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/sonarr.yaml b/templates/compose/sonarr.yaml new file mode 100644 index 000000000..c2ac3e270 --- /dev/null +++ b/templates/compose/sonarr.yaml @@ -0,0 +1,24 @@ +# documentation: https://hub.docker.com/r/linuxserver/sonarr +# slogan: Sonarr⁠ (formerly NZBdrone) is a PVR for usenet and bittorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available. +# tags: media, server, tv +# logo: svgs/sonarr.svg +# port: 8989 + +services: + sonarr: + image: lscr.io/linuxserver/sonarr:latest + environment: + - SERVICE_FQDN_SONARR_8989 + - _APP_URL=$SERVICE_FQDN_SONARR + - PUID=1000 + - PGID=1000 + - TZ=${TZ:-America/Toronto} + volumes: + - sonarr-config:/config + # - sonarr-tv:/tv #optional + # - downloads:/downloads #optional + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8989/ping"] + interval: 2s + timeout: 10s + retries: 15 From 63fb9e9f40843adc60caadbee0bbe403f0bc84b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Tremblay=20Lefran=C3=A7ois?= Date: Mon, 2 Dec 2024 13:18:35 -0500 Subject: [PATCH 05/15] added radarr --- public/svgs/radarr.svg | 1 + templates/compose/radarr.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 public/svgs/radarr.svg create mode 100644 templates/compose/radarr.yaml diff --git a/public/svgs/radarr.svg b/public/svgs/radarr.svg new file mode 100644 index 000000000..93a4c9232 --- /dev/null +++ b/public/svgs/radarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/radarr.yaml b/templates/compose/radarr.yaml new file mode 100644 index 000000000..2795582a6 --- /dev/null +++ b/templates/compose/radarr.yaml @@ -0,0 +1,24 @@ +# documentation: https://hub.docker.com/r/linuxserver/radarr +# slogan: Radarr⁠ - A fork of Sonarr to work with movies à la Couchpotato. +# tags: media, server, movies +# logo: svgs/radarr.svg +# port: 7878 + +services: + radarr: + image: lscr.io/linuxserver/radarr:latest + environment: + - SERVICE_FQDN_RADARR_7878 + - _APP_URL=$SERVICE_FQDN_RADARR + - PUID=1000 + - PGID=1000 + - TZ=${TZ:-America/Toronto} + volumes: + - radarr-config:/config + # - radarr-movies:/movies #optional + # - downloads:/downloads #optional + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:7878/ping"] + interval: 2s + timeout: 10s + retries: 15 From 59fe9de1c932e06e189c5fe8c2d16e8b0ebfc6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Tremblay=20Lefran=C3=A7ois?= Date: Mon, 2 Dec 2024 13:23:55 -0500 Subject: [PATCH 06/15] added prowlarr --- public/svgs/prowlarr.svg | 1 + templates/compose/prowlarr.yaml | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 public/svgs/prowlarr.svg create mode 100644 templates/compose/prowlarr.yaml diff --git a/public/svgs/prowlarr.svg b/public/svgs/prowlarr.svg new file mode 100644 index 000000000..a1a5a8ce3 --- /dev/null +++ b/public/svgs/prowlarr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/compose/prowlarr.yaml b/templates/compose/prowlarr.yaml new file mode 100644 index 000000000..f173ee1ca --- /dev/null +++ b/templates/compose/prowlarr.yaml @@ -0,0 +1,22 @@ +# documentation: https://hub.docker.com/r/linuxserver/prowlarr +# slogan: Prowlarr⁠ is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all). +# tags: media, server, movies, tv, indexer, torrent, nzb, usenet +# logo: svgs/prowlarr.svg +# port: 9696 + +services: + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + environment: + - SERVICE_FQDN_PROWLARR_9696 + - _APP_URL=$SERVICE_FQDN_PROWLARR + - PUID=1000 + - PGID=1000 + - TZ=${TZ:-America/Toronto} + volumes: + - prowlarr-config:/config + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9696/ping"] + interval: 2s + timeout: 10s + retries: 15 From f9143d0500af40d27d3a0d354f5e901169040644 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:14:56 +0100 Subject: [PATCH 07/15] Update service-templates.json --- templates/service-templates.json | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/templates/service-templates.json b/templates/service-templates.json index 0fff92f86..363fe511f 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -2285,6 +2285,24 @@ "minversion": "0.0.0", "port": "8080" }, + "prowlarr": { + "documentation": "https://hub.docker.com/r/linuxserver/prowlarr?utm_source=coolify.io", + "slogan": "Prowlarr\u2060 is a indexer manager/proxy built on the popular arr .net/reactjs base stack to integrate with your various PVR apps. Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management of your indexers with no per app Indexer setup required (we do it all).", + "compose": "c2VydmljZXM6CiAgcHJvd2xhcnI6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvcHJvd2xhcnI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1BST1dMQVJSXzk2OTYKICAgICAgLSBfQVBQX1VSTD0kU0VSVklDRV9GUUROX1BST1dMQVJSCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gJ1RaPSR7VFo6LUFtZXJpY2EvVG9yb250b30nCiAgICB2b2x1bWVzOgogICAgICAtICdwcm93bGFyci1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo5Njk2L3BpbmcnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "tags": [ + "media", + "server", + "movies", + "tv", + "indexer", + "torrent", + "nzb", + "usenet" + ], + "logo": "svgs/prowlarr.svg", + "minversion": "0.0.0", + "port": "9696" + }, "qbittorrent": { "documentation": "https://docs.linuxserver.io/images/docker-qbittorrent/?utm_source=coolify.io", "slogan": "The qBittorrent project aims to provide an open-source software alternative to \u03bcTorrent.", @@ -2333,6 +2351,19 @@ "minversion": "0.0.0", "port": "15672" }, + "radarr": { + "documentation": "https://hub.docker.com/r/linuxserver/radarr?utm_source=coolify.io", + "slogan": "Radarr\u2060 - A fork of Sonarr to work with movies \u00e0 la Couchpotato.", + "compose": "c2VydmljZXM6CiAgcmFkYXJyOgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL3JhZGFycjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUkFEQVJSXzc4NzgKICAgICAgLSBfQVBQX1VSTD0kU0VSVklDRV9GUUROX1JBREFSUgogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtICdUWj0ke1RaOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAncmFkYXJyLWNvbmZpZzovY29uZmlnJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojc4NzgvcGluZycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "tags": [ + "media", + "server", + "movies" + ], + "logo": "svgs/radarr.svg", + "minversion": "0.0.0", + "port": "7878" + }, "reactive-resume": { "documentation": "https://rxresu.me/?utm_source=coolify.io", "slogan": "A one-of-a-kind resume builder that keeps your privacy in mind.", @@ -2473,6 +2504,19 @@ "minversion": "0.0.0", "port": "6001" }, + "sonarr": { + "documentation": "https://hub.docker.com/r/linuxserver/sonarr?utm_source=coolify.io", + "slogan": "Sonarr\u2060 (formerly NZBdrone) is a PVR for usenet and bittorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.", + "compose": "c2VydmljZXM6CiAgc29uYXJyOgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL3NvbmFycjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU09OQVJSXzg5ODkKICAgICAgLSBfQVBQX1VSTD0kU0VSVklDRV9GUUROX1NPTkFSUgogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtICdUWj0ke1RaOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAnc29uYXJyLWNvbmZpZzovY29uZmlnJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojg5ODkvcGluZycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "tags": [ + "media", + "server", + "tv" + ], + "logo": "svgs/sonarr.svg", + "minversion": "0.0.0", + "port": "8989" + }, "statusnook": { "documentation": "https://statusnook.com?utm_source=coolify.io", "slogan": "Effortlessly deploy a status page and start monitoring endpoints in minutes", From a776871b22ffdb6d5c577f5f5dd3bde93f763888 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:35:04 +0100 Subject: [PATCH 08/15] disable coder see #4431 --- templates/compose/coder.yaml | 1 + templates/service-templates.json | 15 --------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/templates/compose/coder.yaml b/templates/compose/coder.yaml index fec22b5bf..717cff03c 100644 --- a/templates/compose/coder.yaml +++ b/templates/compose/coder.yaml @@ -1,3 +1,4 @@ +# ignore: true # documentation: https://coder.com/docs # slogan: Coder is an open-source platform for creating and managing cloud development environments on your infrastructure, with the tools and IDEs your developers already love. # tags: coder,development,environment,self-hosted,postgres diff --git a/templates/service-templates.json b/templates/service-templates.json index 363fe511f..222ca1601 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -418,21 +418,6 @@ "minversion": "0.0.0", "port": "8443" }, - "coder": { - "documentation": "https://coder.com/docs?utm_source=coolify.io", - "slogan": "Coder is an open-source platform for creating and managing cloud development environments on your infrastructure, with the tools and IDEs your developers already love.", - "compose": "c2VydmljZXM6CiAgY29kZXI6CiAgICBpbWFnZTogJ2doY3IuaW8vY29kZXIvY29kZXI6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0NPREVSXzcwODAKICAgICAgLSAnQ09ERVJfUEdfQ09OTkVDVElPTl9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGNvZGVyLWRhdGFiYXNlLyR7UE9TVEdSRVNfREI6LWNvZGVyLWRifT9zc2xtb2RlPWRpc2FibGUnCiAgICAgIC0gJ0NPREVSX0hUVFBfQUREUkVTUz0wLjAuMC4wOjcwODAnCiAgICAgIC0gJ0NPREVSX0FDQ0VTU19VUkw9JHtTRVJWSUNFX0ZRRE5fQ09ERVJ9JwogICAgdm9sdW1lczoKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBkZXBlbmRzX29uOgogICAgICBjb2Rlci1kYXRhYmFzZToKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHdnZXQKICAgICAgICAtICctcScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjcwODAnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBjb2Rlci1kYXRhYmFzZToKICAgIGltYWdlOiAncG9zdGdyZXM6MTYuNC1hbHBpbmUnCiAgICBlbnZpcm9ubWVudDoKICAgICAgUE9TVEdSRVNfVVNFUjogJyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICcke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICBQT1NUR1JFU19EQjogJyR7UE9TVEdSRVNfREI6LWNvZGVyLWRifScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2NvZGVyLXBvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICR7UE9TVEdSRVNfVVNFUjotdXNlcm5hbWV9IC1kICR7UE9TVEdSRVNfREI6LWNvZGVyfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK", - "tags": [ - "coder", - "development", - "environment", - "self-hosted", - "postgres" - ], - "logo": "svgs/coder.svg", - "minversion": "0.0.0", - "port": "7080" - }, "cryptgeon": { "documentation": "https://github.com/cupcakearmy/cryptgeon?utm_source=coolify.io", "slogan": "Secure note / file sharing service inspired by PrivNote.", From d1a1ecbe28ce128173473bd6ba63158d8462455c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 Dec 2024 22:01:15 +0100 Subject: [PATCH 09/15] fix: only able to select the right keys --- app/Livewire/Boarding/Index.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index b6c799c4e..eadabba7c 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -172,13 +172,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== public function getProxyType() { - // Set Default Proxy Type $this->selectProxy(ProxyTypes::TRAEFIK->value); - // $proxyTypeSet = $this->createdServer->proxy->type; - // if (!$proxyTypeSet) { - // $this->currentState = 'select-proxy'; - // return; - // } $this->getProjects(); } @@ -189,7 +183,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== return; } - $this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey); + $this->createdPrivateKey = PrivateKey::where('team_id', currentTeam()->id)->where('id', $this->selectedExistingPrivateKey)->first(); $this->privateKey = $this->createdPrivateKey->private_key; $this->currentState = 'create-server'; } From ec1312a3f8895ac2768ec2c79dceaa30acb2ecc8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 Dec 2024 22:12:02 +0100 Subject: [PATCH 10/15] able to change zoom --- resources/css/app.css | 16 +-------- resources/views/components/navbar.blade.php | 39 +++++++++++++++++++-- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/resources/css/app.css b/resources/css/app.css index c3d46cc04..32d476c1a 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,20 +4,6 @@ @tailwind components; @tailwind utilities; -html { - font-size: 93.75%; -} - -:root { - --vh: 1vh; -} - -@screen lg { - html { - font-size: 87.5%; - } -} - html, body { @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400 w-full; @@ -336,4 +322,4 @@ section { .dz-button { @apply w-full p-4 py-10 my-4 font-bold bg-white border dark:border-coolgray-400 dark:text-white dark:bg-transparent hover:dark:bg-coolgray-400; -} +} \ No newline at end of file diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index f635a6787..7d343c645 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -7,8 +7,13 @@ } window.location.reload(); }, + setZoom(zoom) { + localStorage.setItem('zoom', zoom); + window.location.reload(); + }, init() { this.full = localStorage.getItem('pageWidth'); + this.zoom = localStorage.getItem('zoom'); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { const userSettings = localStorage.getItem('theme'); if (userSettings !== 'system') { @@ -21,6 +26,7 @@ } }); this.queryTheme(); + this.checkZoom(); }, setTheme(type) { this.theme = type; @@ -44,6 +50,30 @@ this.theme = 'system'; document.documentElement.classList.remove('dark'); } + }, + checkZoom() { + if (this.zoom === null) { + this.setZoom(100); + } + if (this.zoom === '90') { + const style = document.createElement('style'); + style.textContent = ` + html { + font-size: 93.75%; + } + + :root { + --vh: 1vh; + } + + @media (min-width: 1024px) { + html { + font-size: 87.5%; + } + } + `; + document.head.appendChild(style); + } } }">

@@ -69,7 +99,7 @@
-
Color
+
Color
@@ -78,6 +108,9 @@ x-show="full === 'full'">Center +
Zoom
+ +
@@ -163,8 +196,8 @@ class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}" href="{{ route('storage.index') }}"> - + From 7f449c3b729c9d89b3c7c86383ba5197fdca1fd8 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:37:11 +0100 Subject: [PATCH 11/15] fix: test email should not be required --- app/Livewire/Notifications/Email.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index fcedf1305..c4e4aae14 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -73,8 +73,8 @@ class Email extends Component #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; - #[Validate(['required', 'email'])] - public string $testEmailAddress = ''; + #[Validate(['nullable', 'email'])] + public ?string $testEmailAddress = null; public function mount() { From 58988d3686cafe60b30a8c5679772a38f39cb3ad Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 Dec 2024 22:49:41 +0100 Subject: [PATCH 12/15] fix: a few inputs --- app/Actions/Docker/GetContainersStatus.php | 10 +- app/Actions/Server/ServerCheck.php | 8 +- .../Api/ApplicationsController.php | 55 +++++- .../Controllers/Api/DatabasesController.php | 21 ++- .../Controllers/Api/ServicesController.php | 4 +- app/Jobs/PushServerUpdateJob.php | 2 +- app/Models/BaseModel.php | 13 ++ app/Models/StandalonePostgresql.php | 16 +- app/Models/Team.php | 7 + bootstrap/helpers/shared.php | 156 ++++++++++-------- 10 files changed, 190 insertions(+), 102 deletions(-) diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 8aae910a9..e9f577e06 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -179,7 +179,7 @@ class GetContainersStatus })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); } } } else { @@ -260,7 +260,7 @@ class GetContainersStatus $environmentName = data_get($service, 'environment.name'); if ($projectUuid && $serviceUuid && $environmentName) { - $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid; + $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/service/' . $serviceUuid; } else { $url = null; } @@ -286,7 +286,7 @@ class GetContainersStatus $environment = data_get($application, 'environment.name'); if ($projectUuid && $applicationUuid && $environment) { - $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid; + $url = base_url() . '/project/' . $projectUuid . '/' . $environment . '/application/' . $applicationUuid; } else { $url = null; } @@ -311,7 +311,7 @@ class GetContainersStatus $applicationUuid = data_get($preview, 'application.uuid'); if ($projectUuid && $applicationUuid && $environmentName) { - $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid; + $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/application/' . $applicationUuid; } else { $url = null; } @@ -336,7 +336,7 @@ class GetContainersStatus $databaseUuid = data_get($database, 'uuid'); if ($projectUuid && $databaseUuid && $environmentName) { - $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid; + $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/database/' . $databaseUuid; } else { $url = null; } diff --git a/app/Actions/Server/ServerCheck.php b/app/Actions/Server/ServerCheck.php index 5f9a1e357..35b4996b4 100644 --- a/app/Actions/Server/ServerCheck.php +++ b/app/Actions/Server/ServerCheck.php @@ -51,7 +51,6 @@ class ServerCheck $containerReplicates = null; $this->isSentinel = true; - } else { ['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers(); // ServerStorageCheckJob::dispatch($this->server); @@ -148,7 +147,6 @@ class ServerCheck } else { $labels = Arr::undot(data_get($container, 'Config.Labels')); } - } $managed = data_get($labels, 'coolify.managed'); if (! $managed) { @@ -215,7 +213,7 @@ class ServerCheck if ($isPublic) { $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) { if ($this->isSentinel) { - return data_get($value, 'name') === $uuid.'-proxy'; + return data_get($value, 'name') === $uuid . '-proxy'; } else { if ($this->server->isSwarm()) { @@ -247,7 +245,7 @@ class ServerCheck if ($isPublic) { $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) { if ($this->isSentinel) { - return data_get($value, 'name') === $uuid.'-proxy'; + return data_get($value, 'name') === $uuid . '-proxy'; } else { if ($this->server->isSwarm()) { return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; @@ -259,7 +257,7 @@ class ServerCheck })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); } } } diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index c69640970..614208c78 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -1591,16 +1591,32 @@ class ApplicationsController extends Controller } $domains = $request->domains; if ($request->has('domains') && $server->isProxyShouldRun()) { - $errors = []; + $uuid = $request->uuid; $fqdn = $request->domains; $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); $fqdn = str($fqdn)->replaceStart(',', '')->trim(); - $application->fqdn = $fqdn; - if (! $application->settings->is_container_label_readonly_enabled) { - $customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); - $application->custom_labels = base64_encode($customLabels); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + $domain = trim($domain); + if (filter_var($domain, FILTER_VALIDATE_URL) === false || !preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}/', $domain)) { + $errors[] = 'Invalid domain: '.$domain; + } + return $domain; + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); } - $request->offsetUnset('domains'); } $dockerComposeDomainsJson = collect(); @@ -2811,3 +2827,30 @@ class ApplicationsController extends Controller } } } + + $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + if (filter_var($domain, FILTER_VALIDATE_URL) === false) { + $errors[] = 'Invalid domain: ' . $domain; + } + + return str($domain)->trim()->lower(); + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); + } + } + } +} diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index ce658d2a2..9366e6300 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -1557,7 +1557,8 @@ class DatabasesController extends Controller ] ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1632,9 +1633,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database starting request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1708,9 +1711,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', @@ -1784,9 +1789,11 @@ class DatabasesController extends Controller type: 'object', properties: [ 'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'], - ]) + ] + ) ), - ]), + ] + ), new OA\Response( response: 401, ref: '#/components/responses/401', diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bf90322e2..abf8a24af 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -308,7 +308,7 @@ class ServicesController extends Controller } if ($oneClickService) { $service_payload = [ - 'name' => "$oneClickServiceName-".str()->random(10), + 'name' => "$oneClickServiceName-" . str()->random(10), 'docker_compose_raw' => base64_decode($oneClickService), 'environment_id' => $environment->id, 'service_type' => $oneClickServiceName, @@ -320,7 +320,7 @@ class ServicesController extends Controller data_set($service_payload, 'connect_to_docker_network', true); } $service = Service::create($service_payload); - $service->name = "$oneClickServiceName-".$service->uuid; + $service->name = "$oneClickServiceName-" . $service->uuid; $service->save(); if ($oneClickDotEnvs?->count() > 0) { $oneClickDotEnvs->each(function ($value) use ($service) { diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 24f8d1e6b..fa146ff96 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -280,7 +280,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue })->first(); if (! $tcpProxyContainerFound) { StartDatabaseProxy::dispatch($database); - $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); } else { } } diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 17201ea6e..500c34fb6 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Visus\Cuid2\Cuid2; @@ -18,4 +19,16 @@ abstract class BaseModel extends Model } }); } + public function name(): Attribute + { + return new Attribute( + get: fn() => sanitize_string($this->getRawOriginal('name')), + ); + } + public function image(): Attribute + { + return new Attribute( + get: fn() => sanitize_string($this->getRawOriginal('image')), + ); + } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 4a457a6cf..99cc8a55b 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -25,7 +25,7 @@ class StandalonePostgresql extends BaseModel { static::created(function ($database) { LocalPersistentVolume::create([ - 'name' => 'postgres-data-'.$database->uuid, + 'name' => 'postgres-data-' . $database->uuid, 'mount_path' => '/var/lib/postgresql/data', 'host_path' => null, 'resource_id' => $database->id, @@ -48,7 +48,7 @@ class StandalonePostgresql extends BaseModel public function workdir() { - return database_configuration_dir()."/{$this->uuid}"; + return database_configuration_dir() . "/{$this->uuid}"; } protected function serverStatus(): Attribute @@ -65,7 +65,7 @@ class StandalonePostgresql extends BaseModel $server = data_get($this, 'destination.server'); $workdir = $this->workdir(); if (str($workdir)->endsWith($this->uuid)) { - instant_remote_process(['rm -rf '.$this->workdir()], $server, false); + instant_remote_process(['rm -rf ' . $this->workdir()], $server, false); } } @@ -82,7 +82,7 @@ class StandalonePostgresql extends BaseModel public function isConfigurationChanged(bool $save = false) { - $newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method; + $newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method; $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); @@ -186,14 +186,14 @@ class StandalonePostgresql extends BaseModel public function portsMappings(): Attribute { return Attribute::make( - set: fn ($value) => $value === '' ? null : $value, + set: fn($value) => $value === '' ? null : $value, ); } public function portsMappingsArray(): Attribute { return Attribute::make( - get: fn () => is_null($this->ports_mappings) + get: fn() => is_null($this->ports_mappings) ? [] : explode(',', $this->ports_mappings), @@ -208,7 +208,7 @@ class StandalonePostgresql extends BaseModel public function databaseType(): Attribute { return new Attribute( - get: fn () => $this->type(), + get: fn() => $this->type(), ); } @@ -220,7 +220,7 @@ class StandalonePostgresql extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", + get: fn() => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", ); } diff --git a/app/Models/Team.php b/app/Models/Team.php index e21aa3a25..76d89fb8e 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -127,6 +127,13 @@ class Team extends Model implements SendsDiscord, SendsEmail ]; } + public function name(): Attribute + { + return new Attribute( + get: fn() => sanitize_string($this->getRawOriginal('name')), + ); + } + public function getRecepients($notification) { $recipients = data_get($notification, 'emails', null); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1a943cd73..ddc578400 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -67,31 +67,51 @@ function base_configuration_dir(): string } function application_configuration_dir(): string { - return base_configuration_dir().'/applications'; + return base_configuration_dir() . '/applications'; } function service_configuration_dir(): string { - return base_configuration_dir().'/services'; + return base_configuration_dir() . '/services'; } function database_configuration_dir(): string { - return base_configuration_dir().'/databases'; + return base_configuration_dir() . '/databases'; } function database_proxy_dir($uuid): string { - return base_configuration_dir()."/databases/$uuid/proxy"; + return base_configuration_dir() . "/databases/$uuid/proxy"; } function backup_dir(): string { - return base_configuration_dir().'/backups'; + return base_configuration_dir() . '/backups'; } function metrics_dir(): string { - return base_configuration_dir().'/metrics'; + return base_configuration_dir() . '/metrics'; +} + +function sanitize_string(string $input): string +{ + // Remove any HTML/PHP tags + $sanitized = strip_tags($input); + + // Convert special characters to HTML entities + $sanitized = htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // Remove any control characters + $sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $sanitized); + + // Trim whitespace + $sanitized = trim($sanitized); + + return $sanitized; } function generate_readme_file(string $name, string $updated_at): string { + $name = sanitize_string($name); + $updated_at = sanitize_string($updated_at); + return "Resource name: $name\nLatest Deployment Date: $updated_at"; } @@ -122,8 +142,8 @@ function refreshSession(?Team $team = null): void $team = User::find(Auth::id())->teams->first(); } } - Cache::forget('team:'.Auth::id()); - Cache::remember('team:'.Auth::id(), 3600, function () use ($team) { + Cache::forget('team:' . Auth::id()); + Cache::remember('team:' . Auth::id(), 3600, function () use ($team) { return $team; }); session(['currentTeam' => $team]); @@ -155,7 +175,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n $message = null; } if ($customErrorMessage) { - $message = $customErrorMessage.' '.$message; + $message = $customErrorMessage . ' ' . $message; } if (isset($livewire)) { @@ -410,7 +430,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null Mail::send( [], [], - fn (Message $message) => $message + fn(Message $message) => $message ->to($email) ->replyTo($email) ->cc($cc) @@ -421,7 +441,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null Mail::send( [], [], - fn (Message $message) => $message + fn(Message $message) => $message ->to($email) ->subject($mail->subject) ->html((string) $mail->render()) @@ -657,19 +677,19 @@ function queryResourcesByUuid(string $uuid) function generateTagDeployWebhook($tag_name) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl).'/api/v1'; + $api = Url::fromString($baseUrl) . '/api/v1'; $endpoint = "/deploy?tag=$tag_name"; - return $api.$endpoint; + return $api . $endpoint; } function generateDeployWebhook($resource) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl).'/api/v1'; + $api = Url::fromString($baseUrl) . '/api/v1'; $endpoint = '/deploy'; $uuid = data_get($resource, 'uuid'); - return $api.$endpoint."?uuid=$uuid&force=false"; + return $api . $endpoint . "?uuid=$uuid&force=false"; } function generateGitManualWebhook($resource, $type) { @@ -679,7 +699,7 @@ function generateGitManualWebhook($resource, $type) if ($resource->getMorphClass() === \App\Models\Application::class) { $baseUrl = base_url(); - return Url::fromString($baseUrl)."/webhooks/source/$type/events/manual"; + return Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual"; } return null; @@ -1047,7 +1067,7 @@ function validate_dns_entry(string $fqdn, Server $server) $query = new DNSQuery($dns_server); $results = $query->query($host, $type); if ($results === false || $query->hasError()) { - ray('Error: '.$query->getLasterror()); + ray('Error: ' . $query->getLasterror()); } else { foreach ($results as $result) { if ($result->getType() == $type) { @@ -1057,7 +1077,7 @@ function validate_dns_entry(string $fqdn, Server $server) break; } if ($result->getData() === $ip) { - ray($host.' has IP address '.$result->getData()); + ray($host . ' has IP address ' . $result->getData()); ray($result->getString()); $found_matching_ip = true; break; @@ -1105,15 +1125,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); if ($uuid) { - $applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); - $serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); + $applications = $applications->filter(fn($app) => $app->uuid !== $uuid); + $serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid); } $domainFound = false; foreach ($applications as $app) { if (is_null($app->fqdn)) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1132,7 +1152,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = if (str($app->fqdn)->isEmpty()) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1182,7 +1202,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null }); $apps = Application::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1201,7 +1221,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null } $apps = ServiceApplication::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1258,7 +1278,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array $commands = $commands->map(function ($line) use ($server) { if (Str::startsWith($line, 'sudo mkdir -p')) { - return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); + return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); } return $line; @@ -1290,7 +1310,7 @@ function parseLineForSudo(string $command, Server $server): string $command = "sudo $command"; } if (Str::startsWith($command, 'sudo mkdir -p')) { - $command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); + $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p'); } if (str($command)->contains('$(') || str($command)->contains('`')) { $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); @@ -1427,9 +1447,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull return $volume; } if (get_class($resource) === \App\Models\Application::class) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; } else { - $dir = base_configuration_dir().'/services/'.$resource->service->uuid; + $dir = base_configuration_dir() . '/services/' . $resource->service->uuid; } if ($source->startsWith('.')) { @@ -1439,7 +1459,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull $source = $source->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) { LocalFileVolume::updateOrCreate( @@ -1897,7 +1917,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (! $isDatabase) { if ($savedService->fqdn) { - data_set($savedService, 'fqdn', $savedService->fqdn.','.$fqdn); + data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn); } else { data_set($savedService, 'fqdn', $fqdn); } @@ -1957,7 +1977,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($resource->server, $containerName); } else { - $fqdn = generateFqdn($resource->server, Str::lower($forService).'-'.$resource->uuid); + $fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -2299,7 +2319,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2307,12 +2327,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2351,7 +2371,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2362,7 +2382,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $read_only = data_get($volume, 'read_only'); if ($source && $target) { if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2370,21 +2390,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } } else { if ($pull_request_id !== 0) { - $source = $source."-pr-$pull_request_id"; + $source = $source . "-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } if (! str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { @@ -2423,7 +2443,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2431,13 +2451,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { $uuid = $resource->uuid; - $name = $uuid."-$name-pr-$pull_request_id"; + $name = $uuid . "-$name-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2456,7 +2476,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { $uuid = $resource->uuid; - $name = str($uuid."-$name"); + $name = str($uuid . "-$name"); $volume = str("$name:$mount"); if ($topLevelVolumes->has($name->value())) { $v = $topLevelVolumes->get($name->value()); @@ -2479,7 +2499,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name."-pr-$pull_request_id"; + $name = $name . "-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2491,7 +2511,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($source && $target) { $uuid = $resource->uuid; if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) { - $dir = base_configuration_dir().'/applications/'.$resource->uuid; + $dir = base_configuration_dir() . '/applications/' . $resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2499,20 +2519,20 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } } else { if ($pull_request_id === 0) { - $source = $uuid."-$source"; + $source = $uuid . "-$source"; } else { - $source = $uuid."-$source-pr-$pull_request_id"; + $source = $uuid . "-$source-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source.':'.$target.':ro'); + data_set($volume, 'source', $source . ':' . $target . ':ro'); } else { - data_set($volume, 'source', $source.':'.$target); + data_set($volume, 'source', $source . ':' . $target); } if (! str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { @@ -2547,7 +2567,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { - return $dependency."-pr-$pull_request_id"; + return $dependency . "-pr-$pull_request_id"; }); data_set($service, 'depends_on', $serviceDependencies->toArray()); } @@ -2720,7 +2740,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($server, $containerName); } else { - $fqdn = generateFqdn($server, Str::lower($forService).'-'.$resource->uuid); + $fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -2924,7 +2944,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if ($pull_request_id !== 0) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { - $services[$serviceName."-pr-$pull_request_id"] = $service; + $services[$serviceName . "-pr-$pull_request_id"] = $service; data_forget($services, $serviceName); }); } @@ -3352,22 +3372,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if ($type->value() === 'bind') { if ($source->value() === '/var/run/docker.sock') { - $volume = $source->value().':'.$target->value(); + $volume = $source->value() . ':' . $target->value(); } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { - $volume = $source->value().':'.$target->value(); + $volume = $source->value() . ':' . $target->value(); } else { if ((int) $resource->compose_parsing_version >= 4) { if ($isApplication) { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); } elseif ($isService) { - $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/services/' . $uuid); } } else { - $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); } $source = replaceLocalSource($source, $mainDirectory); if ($isApplication && $isPullRequest) { - $source = $source."-pr-$pullRequestId"; + $source = $source . "-pr-$pullRequestId"; } LocalFileVolume::updateOrCreate( [ @@ -3387,12 +3407,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if (isDev()) { if ((int) $resource->compose_parsing_version >= 4) { if ($isApplication) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid); } elseif ($isService) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/' . $uuid); } } else { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid); } } $volume = "$source:$target"; @@ -4061,7 +4081,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla $limiterKey, $maxAttempts = 0, function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) { - isDev() && loggy('Rate limit not reached for '.$limiterKey); + isDev() && loggy('Rate limit not reached for ' . $limiterKey); $rateLimited = false; if ($callbackOnSuccess) { @@ -4071,7 +4091,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla $decaySeconds, ); if (! $executed) { - isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.'); + isDev() && loggy('Rate limit reached for ' . $limiterKey . '. Rate limiter will be disabled for ' . $decaySeconds . ' seconds.'); $rateLimited = true; } From 319c3023dcf4e47f1d7919287b3a91393af45178 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 2 Dec 2024 22:49:55 +0100 Subject: [PATCH 13/15] fix --- app/Actions/Docker/GetContainersStatus.php | 8 +- app/Actions/Server/ServerCheck.php | 4 +- .../Controllers/Api/ServicesController.php | 4 +- app/Jobs/PushServerUpdateJob.php | 2 +- app/Models/BaseModel.php | 6 +- app/Models/StandalonePostgresql.php | 16 +-- app/Models/Team.php | 2 +- bootstrap/helpers/shared.php | 136 +++++++++--------- 8 files changed, 90 insertions(+), 88 deletions(-) diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index e9f577e06..706356930 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -260,7 +260,7 @@ class GetContainersStatus $environmentName = data_get($service, 'environment.name'); if ($projectUuid && $serviceUuid && $environmentName) { - $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/service/' . $serviceUuid; + $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid; } else { $url = null; } @@ -286,7 +286,7 @@ class GetContainersStatus $environment = data_get($application, 'environment.name'); if ($projectUuid && $applicationUuid && $environment) { - $url = base_url() . '/project/' . $projectUuid . '/' . $environment . '/application/' . $applicationUuid; + $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid; } else { $url = null; } @@ -311,7 +311,7 @@ class GetContainersStatus $applicationUuid = data_get($preview, 'application.uuid'); if ($projectUuid && $applicationUuid && $environmentName) { - $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/application/' . $applicationUuid; + $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid; } else { $url = null; } @@ -336,7 +336,7 @@ class GetContainersStatus $databaseUuid = data_get($database, 'uuid'); if ($projectUuid && $databaseUuid && $environmentName) { - $url = base_url() . '/project/' . $projectUuid . '/' . $environmentName . '/database/' . $databaseUuid; + $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid; } else { $url = null; } diff --git a/app/Actions/Server/ServerCheck.php b/app/Actions/Server/ServerCheck.php index 35b4996b4..75b8501f3 100644 --- a/app/Actions/Server/ServerCheck.php +++ b/app/Actions/Server/ServerCheck.php @@ -213,7 +213,7 @@ class ServerCheck if ($isPublic) { $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) { if ($this->isSentinel) { - return data_get($value, 'name') === $uuid . '-proxy'; + return data_get($value, 'name') === $uuid.'-proxy'; } else { if ($this->server->isSwarm()) { @@ -245,7 +245,7 @@ class ServerCheck if ($isPublic) { $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) { if ($this->isSentinel) { - return data_get($value, 'name') === $uuid . '-proxy'; + return data_get($value, 'name') === $uuid.'-proxy'; } else { if ($this->server->isSwarm()) { return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index abf8a24af..bf90322e2 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -308,7 +308,7 @@ class ServicesController extends Controller } if ($oneClickService) { $service_payload = [ - 'name' => "$oneClickServiceName-" . str()->random(10), + 'name' => "$oneClickServiceName-".str()->random(10), 'docker_compose_raw' => base64_decode($oneClickService), 'environment_id' => $environment->id, 'service_type' => $oneClickServiceName, @@ -320,7 +320,7 @@ class ServicesController extends Controller data_set($service_payload, 'connect_to_docker_network', true); } $service = Service::create($service_payload); - $service->name = "$oneClickServiceName-" . $service->uuid; + $service->name = "$oneClickServiceName-".$service->uuid; $service->save(); if ($oneClickDotEnvs?->count() > 0) { $oneClickDotEnvs->each(function ($value) use ($service) { diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index fa146ff96..e9fcb2f37 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -280,7 +280,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue })->first(); if (! $tcpProxyContainerFound) { StartDatabaseProxy::dispatch($database); - $this->server->team?->notify(new ContainerRestarted("TCP Proxy for database", $this->server)); + $this->server->team?->notify(new ContainerRestarted('TCP Proxy for database', $this->server)); } else { } } diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 500c34fb6..79801987b 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -19,16 +19,18 @@ abstract class BaseModel extends Model } }); } + public function name(): Attribute { return new Attribute( - get: fn() => sanitize_string($this->getRawOriginal('name')), + get: fn () => sanitize_string($this->getRawOriginal('name')), ); } + public function image(): Attribute { return new Attribute( - get: fn() => sanitize_string($this->getRawOriginal('image')), + get: fn () => sanitize_string($this->getRawOriginal('image')), ); } } diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 99cc8a55b..4a457a6cf 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -25,7 +25,7 @@ class StandalonePostgresql extends BaseModel { static::created(function ($database) { LocalPersistentVolume::create([ - 'name' => 'postgres-data-' . $database->uuid, + 'name' => 'postgres-data-'.$database->uuid, 'mount_path' => '/var/lib/postgresql/data', 'host_path' => null, 'resource_id' => $database->id, @@ -48,7 +48,7 @@ class StandalonePostgresql extends BaseModel public function workdir() { - return database_configuration_dir() . "/{$this->uuid}"; + return database_configuration_dir()."/{$this->uuid}"; } protected function serverStatus(): Attribute @@ -65,7 +65,7 @@ class StandalonePostgresql extends BaseModel $server = data_get($this, 'destination.server'); $workdir = $this->workdir(); if (str($workdir)->endsWith($this->uuid)) { - instant_remote_process(['rm -rf ' . $this->workdir()], $server, false); + instant_remote_process(['rm -rf '.$this->workdir()], $server, false); } } @@ -82,7 +82,7 @@ class StandalonePostgresql extends BaseModel public function isConfigurationChanged(bool $save = false) { - $newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method; + $newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method; $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); $newConfigHash = md5($newConfigHash); $oldConfigHash = data_get($this, 'config_hash'); @@ -186,14 +186,14 @@ class StandalonePostgresql extends BaseModel public function portsMappings(): Attribute { return Attribute::make( - set: fn($value) => $value === '' ? null : $value, + set: fn ($value) => $value === '' ? null : $value, ); } public function portsMappingsArray(): Attribute { return Attribute::make( - get: fn() => is_null($this->ports_mappings) + get: fn () => is_null($this->ports_mappings) ? [] : explode(',', $this->ports_mappings), @@ -208,7 +208,7 @@ class StandalonePostgresql extends BaseModel public function databaseType(): Attribute { return new Attribute( - get: fn() => $this->type(), + get: fn () => $this->type(), ); } @@ -220,7 +220,7 @@ class StandalonePostgresql extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn() => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", + get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", ); } diff --git a/app/Models/Team.php b/app/Models/Team.php index 76d89fb8e..6ba044349 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -130,7 +130,7 @@ class Team extends Model implements SendsDiscord, SendsEmail public function name(): Attribute { return new Attribute( - get: fn() => sanitize_string($this->getRawOriginal('name')), + get: fn () => sanitize_string($this->getRawOriginal('name')), ); } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index ddc578400..d64b5ab6e 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -67,27 +67,27 @@ function base_configuration_dir(): string } function application_configuration_dir(): string { - return base_configuration_dir() . '/applications'; + return base_configuration_dir().'/applications'; } function service_configuration_dir(): string { - return base_configuration_dir() . '/services'; + return base_configuration_dir().'/services'; } function database_configuration_dir(): string { - return base_configuration_dir() . '/databases'; + return base_configuration_dir().'/databases'; } function database_proxy_dir($uuid): string { - return base_configuration_dir() . "/databases/$uuid/proxy"; + return base_configuration_dir()."/databases/$uuid/proxy"; } function backup_dir(): string { - return base_configuration_dir() . '/backups'; + return base_configuration_dir().'/backups'; } function metrics_dir(): string { - return base_configuration_dir() . '/metrics'; + return base_configuration_dir().'/metrics'; } function sanitize_string(string $input): string @@ -142,8 +142,8 @@ function refreshSession(?Team $team = null): void $team = User::find(Auth::id())->teams->first(); } } - Cache::forget('team:' . Auth::id()); - Cache::remember('team:' . Auth::id(), 3600, function () use ($team) { + Cache::forget('team:'.Auth::id()); + Cache::remember('team:'.Auth::id(), 3600, function () use ($team) { return $team; }); session(['currentTeam' => $team]); @@ -175,7 +175,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n $message = null; } if ($customErrorMessage) { - $message = $customErrorMessage . ' ' . $message; + $message = $customErrorMessage.' '.$message; } if (isset($livewire)) { @@ -430,7 +430,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null Mail::send( [], [], - fn(Message $message) => $message + fn (Message $message) => $message ->to($email) ->replyTo($email) ->cc($cc) @@ -441,7 +441,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null Mail::send( [], [], - fn(Message $message) => $message + fn (Message $message) => $message ->to($email) ->subject($mail->subject) ->html((string) $mail->render()) @@ -677,19 +677,19 @@ function queryResourcesByUuid(string $uuid) function generateTagDeployWebhook($tag_name) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl) . '/api/v1'; + $api = Url::fromString($baseUrl).'/api/v1'; $endpoint = "/deploy?tag=$tag_name"; - return $api . $endpoint; + return $api.$endpoint; } function generateDeployWebhook($resource) { $baseUrl = base_url(); - $api = Url::fromString($baseUrl) . '/api/v1'; + $api = Url::fromString($baseUrl).'/api/v1'; $endpoint = '/deploy'; $uuid = data_get($resource, 'uuid'); - return $api . $endpoint . "?uuid=$uuid&force=false"; + return $api.$endpoint."?uuid=$uuid&force=false"; } function generateGitManualWebhook($resource, $type) { @@ -699,7 +699,7 @@ function generateGitManualWebhook($resource, $type) if ($resource->getMorphClass() === \App\Models\Application::class) { $baseUrl = base_url(); - return Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual"; + return Url::fromString($baseUrl)."/webhooks/source/$type/events/manual"; } return null; @@ -1067,7 +1067,7 @@ function validate_dns_entry(string $fqdn, Server $server) $query = new DNSQuery($dns_server); $results = $query->query($host, $type); if ($results === false || $query->hasError()) { - ray('Error: ' . $query->getLasterror()); + ray('Error: '.$query->getLasterror()); } else { foreach ($results as $result) { if ($result->getType() == $type) { @@ -1077,7 +1077,7 @@ function validate_dns_entry(string $fqdn, Server $server) break; } if ($result->getData() === $ip) { - ray($host . ' has IP address ' . $result->getData()); + ray($host.' has IP address '.$result->getData()); ray($result->getString()); $found_matching_ip = true; break; @@ -1125,15 +1125,15 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); if ($uuid) { - $applications = $applications->filter(fn($app) => $app->uuid !== $uuid); - $serviceApplications = $serviceApplications->filter(fn($app) => $app->uuid !== $uuid); + $applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); + $serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); } $domainFound = false; foreach ($applications as $app) { if (is_null($app->fqdn)) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1152,7 +1152,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = if (str($app->fqdn)->isEmpty()) { continue; } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1202,7 +1202,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null }); $apps = Application::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1221,7 +1221,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null } $apps = ServiceApplication::all(); foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn($fqdn) => $fqdn !== ''); + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { if (str($domain)->endsWith('/')) { $domain = str($domain)->beforeLast('/'); @@ -1278,7 +1278,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array $commands = $commands->map(function ($line) use ($server) { if (Str::startsWith($line, 'sudo mkdir -p')) { - return "$line && sudo chown -R $server->user:$server->user " . Str::after($line, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($line, 'sudo mkdir -p'); + return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); } return $line; @@ -1310,7 +1310,7 @@ function parseLineForSudo(string $command, Server $server): string $command = "sudo $command"; } if (Str::startsWith($command, 'sudo mkdir -p')) { - $command = "$command && sudo chown -R $server->user:$server->user " . Str::after($command, 'sudo mkdir -p') . ' && sudo chmod -R o-rwx ' . Str::after($command, 'sudo mkdir -p'); + $command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); } if (str($command)->contains('$(') || str($command)->contains('`')) { $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); @@ -1447,9 +1447,9 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull return $volume; } if (get_class($resource) === \App\Models\Application::class) { - $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + $dir = base_configuration_dir().'/applications/'.$resource->uuid; } else { - $dir = base_configuration_dir() . '/services/' . $resource->service->uuid; + $dir = base_configuration_dir().'/services/'.$resource->service->uuid; } if ($source->startsWith('.')) { @@ -1459,7 +1459,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull $source = $source->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source . "-pr-$pull_request_id"; + $source = $source."-pr-$pull_request_id"; } if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) { LocalFileVolume::updateOrCreate( @@ -1917,7 +1917,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (! $isDatabase) { if ($savedService->fqdn) { - data_set($savedService, 'fqdn', $savedService->fqdn . ',' . $fqdn); + data_set($savedService, 'fqdn', $savedService->fqdn.','.$fqdn); } else { data_set($savedService, 'fqdn', $fqdn); } @@ -1977,7 +1977,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($resource->server, $containerName); } else { - $fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid); + $fqdn = generateFqdn($resource->server, Str::lower($forService).'-'.$resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -2319,7 +2319,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + $dir = base_configuration_dir().'/applications/'.$resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2327,12 +2327,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name . "-pr-$pull_request_id"; + $name = $name."-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { - $name = $name . "-pr-$pull_request_id"; + $name = $name."-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2371,7 +2371,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name . "-pr-$pull_request_id"; + $name = $name."-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2382,7 +2382,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $read_only = data_get($volume, 'read_only'); if ($source && $target) { if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { - $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + $dir = base_configuration_dir().'/applications/'.$resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2390,21 +2390,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $source = $source . "-pr-$pull_request_id"; + $source = $source."-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source . ':' . $target . ':ro'); + data_set($volume, 'source', $source.':'.$target.':ro'); } else { - data_set($volume, 'source', $source . ':' . $target); + data_set($volume, 'source', $source.':'.$target); } } else { if ($pull_request_id !== 0) { - $source = $source . "-pr-$pull_request_id"; + $source = $source."-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source . ':' . $target . ':ro'); + data_set($volume, 'source', $source.':'.$target.':ro'); } else { - data_set($volume, 'source', $source . ':' . $target); + data_set($volume, 'source', $source.':'.$target); } if (! str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { @@ -2443,7 +2443,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($name->startsWith('.') || $name->startsWith('~')) { - $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + $dir = base_configuration_dir().'/applications/'.$resource->uuid; if ($name->startsWith('.')) { $name = $name->replaceFirst('.', $dir); } @@ -2451,13 +2451,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $name->replaceFirst('~', $dir); } if ($pull_request_id !== 0) { - $name = $name . "-pr-$pull_request_id"; + $name = $name."-pr-$pull_request_id"; } $volume = str("$name:$mount"); } else { if ($pull_request_id !== 0) { $uuid = $resource->uuid; - $name = $uuid . "-$name-pr-$pull_request_id"; + $name = $uuid."-$name-pr-$pull_request_id"; $volume = str("$name:$mount"); if ($topLevelVolumes->has($name)) { $v = $topLevelVolumes->get($name); @@ -2476,7 +2476,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } else { $uuid = $resource->uuid; - $name = str($uuid . "-$name"); + $name = str($uuid."-$name"); $volume = str("$name:$mount"); if ($topLevelVolumes->has($name->value())) { $v = $topLevelVolumes->get($name->value()); @@ -2499,7 +2499,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $name = $volume->before(':'); $mount = $volume->after(':'); if ($pull_request_id !== 0) { - $name = $name . "-pr-$pull_request_id"; + $name = $name."-pr-$pull_request_id"; } $volume = str("$name:$mount"); } @@ -2511,7 +2511,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($source && $target) { $uuid = $resource->uuid; if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) { - $dir = base_configuration_dir() . '/applications/' . $resource->uuid; + $dir = base_configuration_dir().'/applications/'.$resource->uuid; if (str($source, '.')) { $source = str($source)->replaceFirst('.', $dir); } @@ -2519,20 +2519,20 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $source = str($source)->replaceFirst('~', $dir); } if ($read_only) { - data_set($volume, 'source', $source . ':' . $target . ':ro'); + data_set($volume, 'source', $source.':'.$target.':ro'); } else { - data_set($volume, 'source', $source . ':' . $target); + data_set($volume, 'source', $source.':'.$target); } } else { if ($pull_request_id === 0) { - $source = $uuid . "-$source"; + $source = $uuid."-$source"; } else { - $source = $uuid . "-$source-pr-$pull_request_id"; + $source = $uuid."-$source-pr-$pull_request_id"; } if ($read_only) { - data_set($volume, 'source', $source . ':' . $target . ':ro'); + data_set($volume, 'source', $source.':'.$target.':ro'); } else { - data_set($volume, 'source', $source . ':' . $target); + data_set($volume, 'source', $source.':'.$target); } if (! str($source)->startsWith('/')) { if ($topLevelVolumes->has($source)) { @@ -2567,7 +2567,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { - return $dependency . "-pr-$pull_request_id"; + return $dependency."-pr-$pull_request_id"; }); data_set($service, 'depends_on', $serviceDependencies->toArray()); } @@ -2740,7 +2740,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($server, $containerName); } else { - $fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid); + $fqdn = generateFqdn($server, Str::lower($forService).'-'.$resource->uuid); } if ($port) { $fqdn = "$fqdn:$port"; @@ -2944,7 +2944,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if ($pull_request_id !== 0) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { - $services[$serviceName . "-pr-$pull_request_id"] = $service; + $services[$serviceName."-pr-$pull_request_id"] = $service; data_forget($services, $serviceName); }); } @@ -3372,22 +3372,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } if ($type->value() === 'bind') { if ($source->value() === '/var/run/docker.sock') { - $volume = $source->value() . ':' . $target->value(); + $volume = $source->value().':'.$target->value(); } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { - $volume = $source->value() . ':' . $target->value(); + $volume = $source->value().':'.$target->value(); } else { if ((int) $resource->compose_parsing_version >= 4) { if ($isApplication) { - $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); } elseif ($isService) { - $mainDirectory = str(base_configuration_dir() . '/services/' . $uuid); + $mainDirectory = str(base_configuration_dir().'/services/'.$uuid); } } else { - $mainDirectory = str(base_configuration_dir() . '/applications/' . $uuid); + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); } $source = replaceLocalSource($source, $mainDirectory); if ($isApplication && $isPullRequest) { - $source = $source . "-pr-$pullRequestId"; + $source = $source."-pr-$pullRequestId"; } LocalFileVolume::updateOrCreate( [ @@ -3407,12 +3407,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if (isDev()) { if ((int) $resource->compose_parsing_version >= 4) { if ($isApplication) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); } elseif ($isService) { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/' . $uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid); } } else { - $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/' . $uuid); + $source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid); } } $volume = "$source:$target"; @@ -4081,7 +4081,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla $limiterKey, $maxAttempts = 0, function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) { - isDev() && loggy('Rate limit not reached for ' . $limiterKey); + isDev() && loggy('Rate limit not reached for '.$limiterKey); $rateLimited = false; if ($callbackOnSuccess) { @@ -4091,7 +4091,7 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla $decaySeconds, ); if (! $executed) { - isDev() && loggy('Rate limit reached for ' . $limiterKey . '. Rate limiter will be disabled for ' . $decaySeconds . ' seconds.'); + isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.'); $rateLimited = true; } From f7ef339ec3f6e5568dbad3fb7220affea58a3e90 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 3 Dec 2024 12:21:09 +0100 Subject: [PATCH 14/15] Improve notification message for TCP Proxy container restart to include database name --- app/Jobs/PushServerUpdateJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index e9fcb2f37..24f8d1e6b 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -280,7 +280,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue })->first(); if (! $tcpProxyContainerFound) { StartDatabaseProxy::dispatch($database); - $this->server->team?->notify(new ContainerRestarted('TCP Proxy for database', $this->server)); + $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); } else { } } From 669293afc1b3f7190a43b279fb62a5118bb9039d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 3 Dec 2024 12:22:27 +0100 Subject: [PATCH 15/15] disable success notifications for now --- app/Jobs/ApplicationDeploymentJob.php | 2 +- app/Jobs/DatabaseBackupJob.php | 2 +- app/Models/Server.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 6a66bb56d..41909fa30 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2400,7 +2400,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); } - $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)); } } diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 89674b255..ee702202f 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -306,7 +306,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue if ($this->backup->save_s3) { $this->upload_to_s3(); } - $this->team?->notify(new BackupSuccess($this->backup, $this->database, $database)); + //$this->team?->notify(new BackupSuccess($this->backup, $this->database, $database)); $this->backup_log->update([ 'status' => 'success', 'message' => $this->backup_output, diff --git a/app/Models/Server.php b/app/Models/Server.php index 27c2b9b99..83b91b254 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1039,7 +1039,7 @@ $schema://$host { $this->unreachable_notification_sent = false; $this->save(); $this->refresh(); - $this->team->notify(new Reachable($this)); + // $this->team->notify(new Reachable($this)); } public function sendUnreachableNotification() @@ -1047,7 +1047,7 @@ $schema://$host { $this->unreachable_notification_sent = true; $this->save(); $this->refresh(); - $this->team->notify(new Unreachable($this)); + // $this->team->notify(new Unreachable($this)); } public function validateConnection(bool $justCheckingNewKey = false)