From 9b65b82ccba58af4691f58ee095298007854bf96 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 31 Jan 2026 01:56:17 +1100 Subject: [PATCH 01/57] feat(template): cloudflare-ddns --- public/svgs/cloudflare-ddns.svg | 8 ++++++++ templates/compose/cloudflare-ddns.yaml | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 public/svgs/cloudflare-ddns.svg create mode 100644 templates/compose/cloudflare-ddns.yaml diff --git a/public/svgs/cloudflare-ddns.svg b/public/svgs/cloudflare-ddns.svg new file mode 100644 index 000000000..efe800bcc --- /dev/null +++ b/public/svgs/cloudflare-ddns.svg @@ -0,0 +1,8 @@ + + + + + + DDNS + + diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml new file mode 100644 index 000000000..874f2cffb --- /dev/null +++ b/templates/compose/cloudflare-ddns.yaml @@ -0,0 +1,20 @@ +# documentation: https://github.com/favonia/cloudflare-ddns +# slogan: A small, feature-rich, and robust Cloudflare DDNS updater. +# category: automation +# tags: cloud, ddns +# logo: svgs/cloudflare-ddns.svg + +services: + cloudflare-ddns: + image: favonia/cloudflare-ddns:1 + network_mode: host + restart: unless-stopped + user: "1000:1000" + read_only: true + cap_drop: [all] + security_opt: [no-new-privileges:true] + environment: + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} + - DOMAINS=${DOMAINS} + - PROXIED=false + - IP6_PROVIDER=none From 90449d2bb5e7b8370dadb0899f511bb9c5b6c2cc Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:55:44 +1100 Subject: [PATCH 02/57] fix: remove restart: unless-stopped Update templates/compose/cloudflare-ddns.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/cloudflare-ddns.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index 874f2cffb..b44828a70 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -8,7 +8,6 @@ services: cloudflare-ddns: image: favonia/cloudflare-ddns:1 network_mode: host - restart: unless-stopped user: "1000:1000" read_only: true cap_drop: [all] From 96b9cd3fa543c4e78554af27607ab231d7122e60 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 13:56:09 +1100 Subject: [PATCH 03/57] fix: mark the API token env as required, and other env as configurable from the UI Update templates/compose/cloudflare-ddns.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/cloudflare-ddns.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index b44828a70..92f857c41 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -13,7 +13,7 @@ services: cap_drop: [all] security_opt: [no-new-privileges:true] environment: - - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?} - DOMAINS=${DOMAINS} - - PROXIED=false - - IP6_PROVIDER=none + - PROXIED=${PROXIED:-false} + - IP6_PROVIDER=${IP6_PROVIDER:-none} From b65f6399df736777a48498aca17c43f9609a5a1f Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:52:14 +1100 Subject: [PATCH 04/57] fix: make domains env compulsory Update templates/compose/cloudflare-ddns.yaml --- templates/compose/cloudflare-ddns.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index 92f857c41..4f29a98d4 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -14,6 +14,6 @@ services: security_opt: [no-new-privileges:true] environment: - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:?} - - DOMAINS=${DOMAINS} + - DOMAINS=${DOMAINS:?} - PROXIED=${PROXIED:-false} - IP6_PROVIDER=${IP6_PROVIDER:-none} From d4e4e446b02c2a0f09bc838e7869ea16138aeffc Mon Sep 17 00:00:00 2001 From: Mohmmad Qunibi Date: Wed, 15 Apr 2026 11:30:43 +0300 Subject: [PATCH 05/57] feat: add emqx service template --- public/svgs/emqx-enterprise.svg | 7 +++++++ templates/compose/emqx-enterprise.yaml | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 public/svgs/emqx-enterprise.svg create mode 100644 templates/compose/emqx-enterprise.yaml diff --git a/public/svgs/emqx-enterprise.svg b/public/svgs/emqx-enterprise.svg new file mode 100644 index 000000000..e67e1bffe --- /dev/null +++ b/public/svgs/emqx-enterprise.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/templates/compose/emqx-enterprise.yaml b/templates/compose/emqx-enterprise.yaml new file mode 100644 index 000000000..1d62ea6f9 --- /dev/null +++ b/templates/compose/emqx-enterprise.yaml @@ -0,0 +1,22 @@ +# documentation: https://www.emqx.io/docs/en/latest/deploy/install-docker.html +# slogan: Open-source MQTT broker for IoT, IIoT, and connected vehicles. +# category: iot +# tags: mqtt,broker,iot,messaging,emqx,iiot +# logo: svgs/emqx-enterprise.svg +# port: 18083 + +services: + emqx: + image: emqx/emqx-enterprise:6.2.0 + environment: + - SERVICE_URL_EMQX_18083 + - EMQX_DASHBOARD__DEFAULT_PASSWORD=${SERVICE_PASSWORD_EMQX} + volumes: + - emqx_data:/opt/emqx/data + - emqx_log:/opt/emqx/log + healthcheck: + test: ["CMD", "/opt/emqx/bin/emqx", "ctl", "status"] + interval: 10s + timeout: 30s + retries: 5 + start_period: 30s From be6acd6f24983d47d3da0d7e0f520a98320e2da7 Mon Sep 17 00:00:00 2001 From: Mohmmad Qunibi <82610649+MohmmadQunibi@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:11:03 +0300 Subject: [PATCH 06/57] Changed the category of emqx to Networking --- templates/compose/emqx-enterprise.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/emqx-enterprise.yaml b/templates/compose/emqx-enterprise.yaml index 1d62ea6f9..0f62c1983 100644 --- a/templates/compose/emqx-enterprise.yaml +++ b/templates/compose/emqx-enterprise.yaml @@ -1,6 +1,6 @@ # documentation: https://www.emqx.io/docs/en/latest/deploy/install-docker.html # slogan: Open-source MQTT broker for IoT, IIoT, and connected vehicles. -# category: iot +# category: Networking # tags: mqtt,broker,iot,messaging,emqx,iiot # logo: svgs/emqx-enterprise.svg # port: 18083 From 950c4e5936c9e5cd179bc913047940bf0825ceaa Mon Sep 17 00:00:00 2001 From: Mohmmad Qunibi <82610649+MohmmadQunibi@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:17:08 +0300 Subject: [PATCH 07/57] Update EMQX image to use 'latest' tag --- templates/compose/emqx-enterprise.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/emqx-enterprise.yaml b/templates/compose/emqx-enterprise.yaml index 0f62c1983..e68c894bf 100644 --- a/templates/compose/emqx-enterprise.yaml +++ b/templates/compose/emqx-enterprise.yaml @@ -7,7 +7,7 @@ services: emqx: - image: emqx/emqx-enterprise:6.2.0 + image: emqx/emqx-enterprise:latest environment: - SERVICE_URL_EMQX_18083 - EMQX_DASHBOARD__DEFAULT_PASSWORD=${SERVICE_PASSWORD_EMQX} From 9208ed102206a67b06ea3dfa978b996106c6500d Mon Sep 17 00:00:00 2001 From: Mohmmad Qunibi <82610649+MohmmadQunibi@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:25:16 +0300 Subject: [PATCH 08/57] Update EMQX image version to 6.2.0 --- templates/compose/emqx-enterprise.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/emqx-enterprise.yaml b/templates/compose/emqx-enterprise.yaml index e68c894bf..0f62c1983 100644 --- a/templates/compose/emqx-enterprise.yaml +++ b/templates/compose/emqx-enterprise.yaml @@ -7,7 +7,7 @@ services: emqx: - image: emqx/emqx-enterprise:latest + image: emqx/emqx-enterprise:6.2.0 environment: - SERVICE_URL_EMQX_18083 - EMQX_DASHBOARD__DEFAULT_PASSWORD=${SERVICE_PASSWORD_EMQX} From 71771c7d3a2265254e92568955596d0c59016e0d Mon Sep 17 00:00:00 2001 From: Mohmmad Qunibi <82610649+MohmmadQunibi@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:25:48 +0300 Subject: [PATCH 09/57] Add ports configuration for EMQX Enterprise --- templates/compose/emqx-enterprise.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/compose/emqx-enterprise.yaml b/templates/compose/emqx-enterprise.yaml index 0f62c1983..01d063b6f 100644 --- a/templates/compose/emqx-enterprise.yaml +++ b/templates/compose/emqx-enterprise.yaml @@ -11,6 +11,11 @@ services: environment: - SERVICE_URL_EMQX_18083 - EMQX_DASHBOARD__DEFAULT_PASSWORD=${SERVICE_PASSWORD_EMQX} + ports: + - "1883:1883" + - "8083:8083" + - "8084:8084" + - "8883:8883" volumes: - emqx_data:/opt/emqx/data - emqx_log:/opt/emqx/log From 655e9f4685a4cf487a0212b26d63f46ccff0737f Mon Sep 17 00:00:00 2001 From: "Mr. Kiter" <107297392+kiterwork@users.noreply.github.com> Date: Tue, 12 May 2026 08:34:57 +0200 Subject: [PATCH 10/57] Update ryot.yaml --- templates/compose/ryot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/ryot.yaml b/templates/compose/ryot.yaml index 56190cd5e..41a9ccf0a 100644 --- a/templates/compose/ryot.yaml +++ b/templates/compose/ryot.yaml @@ -7,7 +7,7 @@ services: ryot: - image: ignisda/ryot:v8 + image: ignisda/ryot:v10.3.0 environment: - SERVICE_URL_RYOT_8000 - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgresql:5432/${POSTGRES_DB} From ea6c63edcf2594181f217216435561ee4c53b8f3 Mon Sep 17 00:00:00 2001 From: "Mr. Kiter" <107297392+kiterwork@users.noreply.github.com> Date: Tue, 12 May 2026 08:58:05 +0200 Subject: [PATCH 11/57] Update jellyfin.yaml --- templates/compose/jellyfin.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/jellyfin.yaml b/templates/compose/jellyfin.yaml index d03b66571..59e36821d 100644 --- a/templates/compose/jellyfin.yaml +++ b/templates/compose/jellyfin.yaml @@ -7,7 +7,7 @@ services: jellyfin: - image: lscr.io/linuxserver/jellyfin:latest + image: lscr.io/linuxserver/jellyfin:10.11.8 environment: - SERVICE_URL_JELLYFIN_8096 - PUID=1000 From b678b5852473d2b55a7c7e5882f32ce696d4c907 Mon Sep 17 00:00:00 2001 From: "Mr. Kiter" <107297392+kiterwork@users.noreply.github.com> Date: Tue, 12 May 2026 09:09:36 +0200 Subject: [PATCH 12/57] Update audiobookshelf.yaml --- templates/compose/audiobookshelf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/audiobookshelf.yaml b/templates/compose/audiobookshelf.yaml index d958f56ff..4bb54710d 100644 --- a/templates/compose/audiobookshelf.yaml +++ b/templates/compose/audiobookshelf.yaml @@ -7,7 +7,7 @@ services: audiobookshelf: - image: ghcr.io/advplyr/audiobookshelf:latest + image: ghcr.io/advplyr/audiobookshelf:2.34.0 environment: - SERVICE_URL_AUDIOBOOKSHELF_80 - TZ=${TIMEZONE:-America/Toronto} From 5267b0ad82152746d62be0a25734065a9e196fef Mon Sep 17 00:00:00 2001 From: "Mr. Kiter" <107297392+kiterwork@users.noreply.github.com> Date: Tue, 12 May 2026 09:10:44 +0200 Subject: [PATCH 13/57] Update grocy.yaml --- templates/compose/grocy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/grocy.yaml b/templates/compose/grocy.yaml index 8a014ce70..a78373fdf 100644 --- a/templates/compose/grocy.yaml +++ b/templates/compose/grocy.yaml @@ -6,7 +6,7 @@ services: grocy: - image: lscr.io/linuxserver/grocy:latest + image: lscr.io/linuxserver/grocy:4.6.0 environment: - SERVICE_URL_GROCY - PUID=1000 From dd19d81e491b60b7e8e51d40a82c6b4ebcd1588f Mon Sep 17 00:00:00 2001 From: "Mr. Kiter" <107297392+kiterwork@users.noreply.github.com> Date: Tue, 12 May 2026 09:22:15 +0200 Subject: [PATCH 14/57] Update mealie.yaml --- templates/compose/mealie.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/mealie.yaml b/templates/compose/mealie.yaml index 7f1121613..a2034bce5 100644 --- a/templates/compose/mealie.yaml +++ b/templates/compose/mealie.yaml @@ -7,7 +7,7 @@ services: mealie: - image: 'ghcr.io/mealie-recipes/mealie:latest' + image: 'ghcr.io/mealie-recipes/mealie:3.17.0' environment: - SERVICE_URL_MEALIE_9000 - ALLOW_SIGNUP=${ALLOW_SIGNUP:-true} From 8c0ecedda426616681112686006a339212d598fe Mon Sep 17 00:00:00 2001 From: toanalien Date: Sat, 16 May 2026 08:40:10 +0200 Subject: [PATCH 15/57] feat(templates): add Hermes Agent + WebUI one-click service Two-container template: hermes-agent gateway plus the hermes-webui chat UI. The WebUI is public-facing (gets the generated FQDN and password via Coolify magic vars); the agent stays internal, sharing named volumes. Hermes uses embedded SQLite, so no external database is needed. --- templates/compose/hermes-agent.yaml | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 templates/compose/hermes-agent.yaml diff --git a/templates/compose/hermes-agent.yaml b/templates/compose/hermes-agent.yaml new file mode 100644 index 000000000..d0a7ec6d3 --- /dev/null +++ b/templates/compose/hermes-agent.yaml @@ -0,0 +1,56 @@ +# documentation: https://github.com/nesquena/hermes-webui +# slogan: Hermes Agent — autonomous AI agent with persistent memory, scheduling, and a self-hosted web chat UI. +# category: ai +# tags: ai, agent, llm, chatbot, hermes, openrouter, anthropic, openai +# logo: svgs/default.webp +# port: 8787 + +services: + hermes-agent: + image: nousresearch/hermes-agent:latest + command: gateway run + environment: + - HERMES_HOME=/home/hermes/.hermes + - HERMES_UID=1000 + - HERMES_GID=1000 + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} + - OPENAI_API_KEY=${OPENAI_API_KEY} + - GOOGLE_API_KEY=${GOOGLE_API_KEY} + volumes: + - hermes-home:/home/hermes/.hermes + - hermes-agent-src:/opt/hermes + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "test -d /home/hermes/.hermes || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + hermes-webui: + image: ghcr.io/nesquena/hermes-webui:latest + depends_on: + - hermes-agent + environment: + - SERVICE_FQDN_HERMESWEBUI_8787 + - HERMES_WEBUI_HOST=0.0.0.0 + - HERMES_WEBUI_PORT=8787 + - HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui + - WANTED_UID=1000 + - WANTED_GID=1000 + - HERMES_WEBUI_PASSWORD=${SERVICE_PASSWORD_HERMESWEBUI} + volumes: + - hermes-home:/home/hermeswebui/.hermes + - hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent + - hermes-workspace:/workspace + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8787/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + hermes-home: + hermes-agent-src: + hermes-workspace: From 7dd6d2b13cfa39ffd21d87c635cb0c45cb3daaff Mon Sep 17 00:00:00 2001 From: Tam Nguyen Date: Mon, 18 May 2026 14:52:15 +1000 Subject: [PATCH 16/57] deps: bump cloudflare-ddns to v2.1.2 --- templates/compose/cloudflare-ddns.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index 4f29a98d4..e66c33a93 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -6,7 +6,7 @@ services: cloudflare-ddns: - image: favonia/cloudflare-ddns:1 + image: favonia/cloudflare-ddns:2.1.2 network_mode: host user: "1000:1000" read_only: true From bce0c51d3709449404c1c61b341a582209f0449c Mon Sep 17 00:00:00 2001 From: Tam Nguyen Date: Mon, 18 May 2026 15:42:31 +1000 Subject: [PATCH 17/57] fix: cloudflare-ddns 1.16.2 --- templates/compose/cloudflare-ddns.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/cloudflare-ddns.yaml b/templates/compose/cloudflare-ddns.yaml index e66c33a93..d7f15effb 100644 --- a/templates/compose/cloudflare-ddns.yaml +++ b/templates/compose/cloudflare-ddns.yaml @@ -6,7 +6,7 @@ services: cloudflare-ddns: - image: favonia/cloudflare-ddns:2.1.2 + image: favonia/cloudflare-ddns:1.16.2 network_mode: host user: "1000:1000" read_only: true From 978d46739d1138833de7071b3882269a4541694b Mon Sep 17 00:00:00 2001 From: Alexandru Furculita Date: Tue, 19 May 2026 10:15:33 +0300 Subject: [PATCH 18/57] feat(service): add openobserve template Adds OpenObserve as a one-click service template. OpenObserve is a cloud-native observability platform for logs, metrics, traces, RUM and session replays, positioned as a self-hosted alternative to Elasticsearch, Splunk and Datadog. - Uses the official open-source image (public.ecr.aws/zinclabs/openobserve) - Wires admin password through Coolify's SERVICE_PASSWORD_* magic env - Persists /data via a named volume - Exposes port 5080 via SERVICE_URL_OPENOBSERVE_5080 - Opts out of telemetry by default (overridable via ZO_TELEMETRY) - Adds /healthz healthcheck and the OpenObserve logo Supersedes #6328, addressing the prior review feedback (drop the deprecated version key, drop hardcoded container_name and restart policy, switch to the magic password env, and use a named volume). --- public/svgs/openobserve.svg | 39 ++++++++++++++++++++++++++++++ templates/compose/openobserve.yaml | 25 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 public/svgs/openobserve.svg create mode 100644 templates/compose/openobserve.yaml diff --git a/public/svgs/openobserve.svg b/public/svgs/openobserve.svg new file mode 100644 index 000000000..c687d948b --- /dev/null +++ b/public/svgs/openobserve.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/compose/openobserve.yaml b/templates/compose/openobserve.yaml new file mode 100644 index 000000000..93239aa19 --- /dev/null +++ b/templates/compose/openobserve.yaml @@ -0,0 +1,25 @@ +# documentation: https://openobserve.ai/docs/ +# slogan: Cloud-native observability platform for logs, metrics, traces, RUM, errors and session replays — a 140x cheaper alternative to Elasticsearch, Splunk and Datadog. +# category: monitoring +# tags: logs, metrics, traces, observability, monitoring, opentelemetry, otel, elasticsearch, splunk, datadog +# logo: svgs/openobserve.svg +# port: 5080 + +services: + openobserve: + image: public.ecr.aws/zinclabs/openobserve:latest + environment: + - SERVICE_URL_OPENOBSERVE_5080 + - ZO_DATA_DIR=/data + - ZO_ROOT_USER_EMAIL=${ZO_ROOT_USER_EMAIL:-root@example.com} + - ZO_ROOT_USER_PASSWORD=${SERVICE_PASSWORD_OPENOBSERVE} + - ZO_TELEMETRY=${ZO_TELEMETRY:-false} + - ZO_COOKIE_SECURE_ONLY=${ZO_COOKIE_SECURE_ONLY:-true} + volumes: + - openobserve-data:/data + healthcheck: + test: ["CMD", "/openobserve", "node", "status"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 60s From e7853656c303710ab5366687e503ae22c228ca76 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Tue, 19 May 2026 16:40:18 +0530 Subject: [PATCH 19/57] fix(service): pin image to static version for open observe --- templates/compose/openobserve.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/openobserve.yaml b/templates/compose/openobserve.yaml index 93239aa19..295491a20 100644 --- a/templates/compose/openobserve.yaml +++ b/templates/compose/openobserve.yaml @@ -7,7 +7,7 @@ services: openobserve: - image: public.ecr.aws/zinclabs/openobserve:latest + image: public.ecr.aws/zinclabs/openobserve:v0.90.0 environment: - SERVICE_URL_OPENOBSERVE_5080 - ZO_DATA_DIR=/data From b64968d50359c38346b90a17fc136858a34086c7 Mon Sep 17 00:00:00 2001 From: toanalien Date: Tue, 19 May 2026 18:55:11 +0700 Subject: [PATCH 20/57] fix(templates): pin image versions and fix magic variable for hermes-agent Address PR review: pin Docker images to v0.14.0 and v0.51.92, change SERVICE_FQDN to SERVICE_URL (generator auto-converts). --- templates/compose/hermes-agent.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/hermes-agent.yaml b/templates/compose/hermes-agent.yaml index d0a7ec6d3..6522e05d5 100644 --- a/templates/compose/hermes-agent.yaml +++ b/templates/compose/hermes-agent.yaml @@ -7,7 +7,7 @@ services: hermes-agent: - image: nousresearch/hermes-agent:latest + image: nousresearch/hermes-agent:v0.14.0 command: gateway run environment: - HERMES_HOME=/home/hermes/.hermes @@ -28,11 +28,11 @@ services: retries: 5 hermes-webui: - image: ghcr.io/nesquena/hermes-webui:latest + image: ghcr.io/nesquena/hermes-webui:v0.51.92 depends_on: - hermes-agent environment: - - SERVICE_FQDN_HERMESWEBUI_8787 + - SERVICE_URL_HERMESWEBUI_8787 - HERMES_WEBUI_HOST=0.0.0.0 - HERMES_WEBUI_PORT=8787 - HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui From 70c187ea40536a3656c3b80738274698299db0a6 Mon Sep 17 00:00:00 2001 From: toanalien Date: Tue, 19 May 2026 19:00:41 +0700 Subject: [PATCH 21/57] fix(templates): add hermes-agent logo and mount agent-src read-only Add official Hermes Agent logo (256x256 PNG from upstream repo). Mount hermes-agent-src volume as read-only in webui container per upstream recommendation (since v0.51.84). --- public/svgs/hermes-agent.png | Bin 0 -> 39138 bytes templates/compose/hermes-agent.yaml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 public/svgs/hermes-agent.png diff --git a/public/svgs/hermes-agent.png b/public/svgs/hermes-agent.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4a8e82ac5693f10fe763ae398aee596ab8009f GIT binary patch literal 39138 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 xu9 Date: Tue, 19 May 2026 19:23:53 +0200 Subject: [PATCH 22/57] chore(gitea-runner): bumped patch version fix: reverted quote autoformat --- templates/compose/gitea-runner.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/gitea-runner.yaml b/templates/compose/gitea-runner.yaml index 81cce4492..42bb21984 100644 --- a/templates/compose/gitea-runner.yaml +++ b/templates/compose/gitea-runner.yaml @@ -6,7 +6,7 @@ services: runner: - image: 'docker.io/gitea/runner:1.0.0' + image: 'docker.io/gitea/runner:1.0.4' environment: - 'GITEA_INSTANCE_URL=${GITEA_INSTANCE_URL}' - 'GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}' From 597a2d806f8c21934d48fb7766ba2e59cda16054 Mon Sep 17 00:00:00 2001 From: toanalien Date: Wed, 20 May 2026 01:05:14 +0700 Subject: [PATCH 23/57] fix(templates): correct image tags for hermes-agent and hermes-webui Pin hermes-agent to sha-273ff5c (no semver tags on Docker Hub). Fix hermes-webui tag from v0.51.92 to 0.51.92 (GHCR has no v prefix). --- templates/compose/hermes-agent.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/hermes-agent.yaml b/templates/compose/hermes-agent.yaml index 848a476e2..9f7bceda4 100644 --- a/templates/compose/hermes-agent.yaml +++ b/templates/compose/hermes-agent.yaml @@ -7,7 +7,7 @@ services: hermes-agent: - image: nousresearch/hermes-agent:v0.14.0 + image: nousresearch/hermes-agent:sha-273ff5c4a47af4499bbe5e3b1139efd313995554 command: gateway run environment: - HERMES_HOME=/home/hermes/.hermes @@ -28,7 +28,7 @@ services: retries: 5 hermes-webui: - image: ghcr.io/nesquena/hermes-webui:v0.51.92 + image: ghcr.io/nesquena/hermes-webui:0.51.92 depends_on: - hermes-agent environment: From 9264f391cb771c0e349a34edec0820b511a81a42 Mon Sep 17 00:00:00 2001 From: toanalien Date: Wed, 20 May 2026 12:04:26 +0700 Subject: [PATCH 24/57] fix(templates): address review feedback for hermes-agent template - Remove top-level volumes block (Coolify auto-generates it) - Remove redundant restart: unless-stopped (Coolify default) - Rename hermes-agent.yaml to hermes-agent-with-webui.yaml --- .../{hermes-agent.yaml => hermes-agent-with-webui.yaml} | 7 ------- 1 file changed, 7 deletions(-) rename templates/compose/{hermes-agent.yaml => hermes-agent-with-webui.yaml} (93%) diff --git a/templates/compose/hermes-agent.yaml b/templates/compose/hermes-agent-with-webui.yaml similarity index 93% rename from templates/compose/hermes-agent.yaml rename to templates/compose/hermes-agent-with-webui.yaml index 9f7bceda4..2d30396d8 100644 --- a/templates/compose/hermes-agent.yaml +++ b/templates/compose/hermes-agent-with-webui.yaml @@ -20,7 +20,6 @@ services: volumes: - hermes-home:/home/hermes/.hermes - hermes-agent-src:/opt/hermes - restart: unless-stopped healthcheck: test: ["CMD-SHELL", "test -d /home/hermes/.hermes || exit 1"] interval: 10s @@ -43,14 +42,8 @@ services: - hermes-home:/home/hermeswebui/.hermes - hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent:ro - hermes-workspace:/workspace - restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8787/health"] interval: 30s timeout: 5s retries: 3 - -volumes: - hermes-home: - hermes-agent-src: - hermes-workspace: From 9b977b9e4d12348c0172c9f73e3a9ab3bd430b0c Mon Sep 17 00:00:00 2001 From: michalzard Date: Thu, 21 May 2026 19:59:14 +0200 Subject: [PATCH 25/57] chore(gitea-runner): bumped version to 1.0.5 --- templates/compose/gitea-runner.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/gitea-runner.yaml b/templates/compose/gitea-runner.yaml index 42bb21984..712424881 100644 --- a/templates/compose/gitea-runner.yaml +++ b/templates/compose/gitea-runner.yaml @@ -6,7 +6,7 @@ services: runner: - image: 'docker.io/gitea/runner:1.0.4' + image: 'docker.io/gitea/runner:1.0.5' environment: - 'GITEA_INSTANCE_URL=${GITEA_INSTANCE_URL}' - 'GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}' From bd744eb8dd9c3071c2c4b4fd7498faa4825764bb Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Fri, 22 May 2026 21:22:50 +0530 Subject: [PATCH 26/57] fix(ui): configuration changes modal values, colors and spacing --- app/Models/Application.php | 21 ++++--- .../ApplicationConfigurationSnapshot.php | 4 +- .../ConfigurationDiffer.php | 4 +- .../deployment/configuration-diff.blade.php | 58 +++++++++---------- .../ApplicationConfigurationChangedTest.php | 14 +++-- .../Livewire/ConfigurationCheckerTest.php | 7 +-- .../ApplicationConfigurationSnapshotTest.php | 8 +-- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 97b257752..fd7f486b9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1188,17 +1188,20 @@ class Application extends BaseModel $currentSnapshot = $this->deploymentConfigurationSnapshot(); $lastDeployment = $this->get_last_successful_deployment(); - if ($lastDeployment?->configuration_snapshot) { - return app(ConfigurationDiffer::class)->diff($lastDeployment->configuration_snapshot, $currentSnapshot); + $previousSnapshot = $lastDeployment?->configuration_snapshot; + + if (! $previousSnapshot) { + $oldConfigHash = data_get($this, 'config_hash'); + $hasLegacyChange = $oldConfigHash === null || $oldConfigHash !== $this->legacyConfigurationHash(); + + if (! $hasLegacyChange) { + return ConfigurationDiff::unchanged(); + } + + $previousSnapshot = []; } - $oldConfigHash = data_get($this, 'config_hash'); - - if ($oldConfigHash === null) { - return ConfigurationDiff::legacy(true); - } - - return ConfigurationDiff::legacy($oldConfigHash !== $this->legacyConfigurationHash()); + return app(ConfigurationDiffer::class)->diff($previousSnapshot, $currentSnapshot); } public function hasPendingDeploymentConfigurationChanges(): bool diff --git a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php index 676b22b6c..8369f9a90 100644 --- a/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php +++ b/app/Services/DeploymentConfiguration/ApplicationConfigurationSnapshot.php @@ -306,7 +306,7 @@ class ApplicationConfigurationSnapshot private function displayValue(mixed $value): string { if ($value === null) { - return 'Not set'; + return '-'; } if (is_bool($value)) { @@ -323,7 +323,7 @@ class ApplicationConfigurationSnapshot private function summarizeText(?string $value): string { if (blank($value)) { - return 'Not set'; + return '-'; } $value = trim((string) $value); diff --git a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php index 27e8d4c3f..b101b9d5b 100644 --- a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php +++ b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php @@ -37,8 +37,8 @@ class ConfigurationDiffer 'impact' => data_get($item, 'impact', 'redeploy'), 'sensitive' => $sensitive, 'display_summary' => $displaySummary, - 'old_display_value' => $sensitive ? ($previous === null ? 'Not set' : 'Set') : data_get($previous, 'display_value', 'Not set'), - 'new_display_value' => $sensitive ? ($current === null ? 'Removed' : 'Set') : data_get($current, 'display_value', 'Not set'), + 'old_display_value' => $sensitive ? ($previous === null ? '-' : '••••••••') : data_get($previous, 'display_value', '-'), + 'new_display_value' => $sensitive ? ($current === null ? '-' : '••••••••') : data_get($current, 'display_value', '-'), ]; } diff --git a/resources/views/components/deployment/configuration-diff.blade.php b/resources/views/components/deployment/configuration-diff.blade.php index ffc0cd34a..f01481057 100644 --- a/resources/views/components/deployment/configuration-diff.blade.php +++ b/resources/views/components/deployment/configuration-diff.blade.php @@ -4,9 +4,9 @@ ]) @php - $changes = data_get($diff, 'changes', []); - $count = data_get($diff, 'count', count($changes)); - $requiresBuild = data_get($diff, 'requires_build', false); + $changes = collect(data_get($diff, 'changes', []))->filter(fn ($change) => data_get($change, 'key') !== 'domains.custom_labels')->values()->all(); + $count = count($changes); + $requiresBuild = collect($changes)->contains(fn ($change) => data_get($change, 'impact') === 'build'); @endphp @if ($count > 0) @@ -21,45 +21,39 @@ 'bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300' => $requiresBuild, 'bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300' => ! $requiresBuild, ])> - {{ $requiresBuild ? 'Rebuild' : 'Redeploy' }} + {{ $requiresBuild ? 'Rebuild required' : 'Redeploy required' }} @unless ($compact) -
+
@foreach (collect($changes)->groupBy('section_label') as $sectionLabel => $sectionChanges)
{{ $sectionLabel }}
-
-
-
-
Field
-
Type
-
From
-
-
To
-
-
- @foreach ($sectionChanges as $change) -
-
- {{ data_get($change, 'label') }} -
-
- {{ data_get($change, 'type') }} -
-
- {{ data_get($change, 'old_display_value') }} -
-
-
- {{ data_get($change, 'new_display_value') }} -
+
+
+
Field
+
From
+
+
To
+
+
+ @foreach ($sectionChanges as $change) +
+
+ {{ data_get($change, 'label') }}
- @endforeach -
+
+ {{ data_get($change, 'old_display_value') }} +
+
+
+ {{ data_get($change, 'new_display_value') }} +
+
+ @endforeach
diff --git a/tests/Feature/ApplicationConfigurationChangedTest.php b/tests/Feature/ApplicationConfigurationChangedTest.php index f862f840d..b91e9f289 100644 --- a/tests/Feature/ApplicationConfigurationChangedTest.php +++ b/tests/Feature/ApplicationConfigurationChangedTest.php @@ -80,11 +80,11 @@ it('checks legacy preview deployment configuration hash using preview environmen $diff = $application->pendingDeploymentConfigurationDiff(); - expect($diff->isLegacyFallback())->toBeTrue() - ->and($diff->isChanged())->toBeTrue(); + expect($diff->isChanged())->toBeTrue() + ->and($diff->count())->toBeGreaterThan(0); }); -it('falls back to legacy configuration hash when no deployment snapshot exists', function () { +it('falls back to real diff against empty snapshot when no deployment snapshot exists', function () { $application = configurationChangedTestApplication(); $application->isConfigurationChanged(save: true); @@ -92,6 +92,10 @@ it('falls back to legacy configuration hash when no deployment snapshot exists', $application->update(['build_command' => 'pnpm build']); - expect($application->refresh()->pendingDeploymentConfigurationDiff()->isLegacyFallback())->toBeTrue() - ->and($application->pendingDeploymentConfigurationDiff()->isChanged())->toBeTrue(); + $diff = $application->refresh()->pendingDeploymentConfigurationDiff(); + + expect($diff->isChanged())->toBeTrue() + ->and($diff->isLegacyFallback())->toBeFalse() + ->and($diff->count())->toBeGreaterThan(0) + ->and(collect($diff->changes())->pluck('label')->toArray())->toContain('Build command'); }); diff --git a/tests/Feature/Livewire/ConfigurationCheckerTest.php b/tests/Feature/Livewire/ConfigurationCheckerTest.php index edf8c5044..d9e6729c8 100644 --- a/tests/Feature/Livewire/ConfigurationCheckerTest.php +++ b/tests/Feature/Livewire/ConfigurationCheckerTest.php @@ -126,8 +126,7 @@ it('does not render environment variable secret values', function () { Livewire::test(ConfigurationChecker::class, ['resource' => $application->refresh()]) ->assertSee('API_TOKEN') - ->assertSee('changed') - ->assertSee('Set') + ->assertSee('••••••••') ->assertDontSee('Hidden') ->assertDontSee('old-secret') ->assertDontSee('new-secret'); @@ -150,9 +149,9 @@ it('renders added environment variables as set without exposing secret values', Livewire::test(ConfigurationChecker::class, ['resource' => $application->refresh()]) ->assertSee('API_TOKEN') ->assertSee('From') - ->assertSee('Not set') + ->assertSee('-') ->assertSee('To') - ->assertSee('Set') + ->assertSee('••••••••') ->assertDontSee('Hidden') ->assertDontSee('new-secret'); }); diff --git a/tests/Unit/DeploymentConfiguration/ApplicationConfigurationSnapshotTest.php b/tests/Unit/DeploymentConfiguration/ApplicationConfigurationSnapshotTest.php index 2106697b2..20b7c0adc 100644 --- a/tests/Unit/DeploymentConfiguration/ApplicationConfigurationSnapshotTest.php +++ b/tests/Unit/DeploymentConfiguration/ApplicationConfigurationSnapshotTest.php @@ -93,8 +93,8 @@ it('detects environment variable value changes without exposing secret values', expect($change)->not->toBeNull() ->and($change['display_summary'])->toBe('Changed') - ->and($change['old_display_value'])->toBe('Set') - ->and($change['new_display_value'])->toBe('Set') + ->and($change['old_display_value'])->toBe('••••••••') + ->and($change['new_display_value'])->toBe('••••••••') ->and(json_encode($diff->toArray()))->not->toContain('old-secret')->not->toContain('new-secret'); }); @@ -117,7 +117,7 @@ it('describes added environment variables as set without exposing secret values' expect($change)->not->toBeNull() ->and($change['display_summary'])->toBeNull() - ->and($change['old_display_value'])->toBe('Not set') - ->and($change['new_display_value'])->toBe('Set') + ->and($change['old_display_value'])->toBe('-') + ->and($change['new_display_value'])->toBe('••••••••') ->and(json_encode($diff->toArray()))->not->toContain('new-secret'); }); From 9c5c39334a46ed5a302ba5c466a6db89bfb35c0a Mon Sep 17 00:00:00 2001 From: michalzard Date: Mon, 25 May 2026 16:02:48 +0200 Subject: [PATCH 27/57] chore(gitea-runner): bumped version to 1.0.6 --- templates/compose/gitea-runner.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/gitea-runner.yaml b/templates/compose/gitea-runner.yaml index 712424881..4cf5ac503 100644 --- a/templates/compose/gitea-runner.yaml +++ b/templates/compose/gitea-runner.yaml @@ -6,7 +6,7 @@ services: runner: - image: 'docker.io/gitea/runner:1.0.5' + image: 'docker.io/gitea/runner:1.0.6' environment: - 'GITEA_INSTANCE_URL=${GITEA_INSTANCE_URL}' - 'GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_REGISTRATION_TOKEN}' From 21db1fd3745694c735410bec986c723795774555 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 26 May 2026 11:41:04 +0200 Subject: [PATCH 28/57] fix(sync-bunny): sync nightly CDN files to nested paths Write nightly versions and releases under json/nightly in the CDN repo, and cover both release and versions-only sync flows with feature tests. --- app/Console/Commands/SyncBunny.php | 9 +-- templates/service-templates-latest.json | 81 +++++++++++++++++++++++-- templates/service-templates.json | 81 +++++++++++++++++++++++-- tests/Feature/SyncBunnyTest.php | 68 +++++++++++++++++++++ 4 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 tests/Feature/SyncBunnyTest.php diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index 9ac3371e0..50bdfaf1e 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -212,7 +212,8 @@ class SyncBunny extends Command $timestamp = time(); $tmpDir = sys_get_temp_dir().'/coolify-cdn-combined-'.$timestamp; $branchName = 'update-releases-and-versions-'.$timestamp; - $versionsTargetPath = $nightly ? 'json/versions-nightly.json' : 'json/versions.json'; + $versionsTargetPath = $nightly ? 'json/nightly/versions.json' : 'json/versions.json'; + $releasesTargetPath = $nightly ? 'json/nightly/releases.json' : 'json/releases.json'; // 3. Clone the repository $this->info('Cloning coolify-cdn repository...'); @@ -237,7 +238,7 @@ class SyncBunny extends Command // 5. Write releases.json $this->info('Writing releases.json...'); - $releasesPath = "$tmpDir/json/releases.json"; + $releasesPath = "$tmpDir/$releasesTargetPath"; $releasesDir = dirname($releasesPath); if (! is_dir($releasesDir)) { @@ -282,7 +283,7 @@ class SyncBunny extends Command // 7. Stage both files $this->info('Staging changes...'); $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git add json/releases.json '.escapeshellarg($versionsTargetPath).' 2>&1', $output, $returnCode); + exec('cd '.escapeshellarg($tmpDir).' && git add '.escapeshellarg($releasesTargetPath).' '.escapeshellarg($versionsTargetPath).' 2>&1', $output, $returnCode); if ($returnCode !== 0) { $this->error('Failed to stage changes: '.implode("\n", $output)); exec('rm -rf '.escapeshellarg($tmpDir)); @@ -539,7 +540,7 @@ class SyncBunny extends Command $timestamp = time(); $tmpDir = sys_get_temp_dir().'/coolify-cdn-versions-'.$timestamp; $branchName = 'update-versions-'.$timestamp; - $targetPath = $nightly ? 'json/versions-nightly.json' : 'json/versions.json'; + $targetPath = $nightly ? 'json/nightly/versions.json' : 'json/versions.json'; // Clone the repository $this->info('Cloning coolify-cdn repository...'); diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index d1cebb2ca..b97e5ef9a 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -171,7 +171,7 @@ "audiobookshelf": { "documentation": "https://www.audiobookshelf.org/?utm_source=coolify.io", "slogan": "Self-hosted audiobook, ebook, and podcast server", - "compose": "c2VydmljZXM6CiAgYXVkaW9ib29rc2hlbGY6CiAgICBpbWFnZTogJ2doY3IuaW8vYWR2cGx5ci9hdWRpb2Jvb2tzaGVsZjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9BVURJT0JPT0tTSEVMRl84MAogICAgICAtICdUWj0ke1RJTUVaT05FOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtYXVkaW9ib29rczovYXVkaW9ib29rcycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtcG9kY2FzdHM6L3BvZGNhc3RzJwogICAgICAtICdhdWRpb2Jvb2tzaGVsZi1jb25maWc6L2NvbmZpZycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtbWV0YWRhdGE6L21ldGFkYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC0tcXVpZXQgLS10cmllcz0xIC0tdGltZW91dD01IGh0dHA6Ly9sb2NhbGhvc3Q6ODAvcGluZyAtTyAvZGV2L251bGwgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgICAgIHN0YXJ0X3BlcmlvZDogMTVzCg==", + "compose": "c2VydmljZXM6CiAgYXVkaW9ib29rc2hlbGY6CiAgICBpbWFnZTogJ2doY3IuaW8vYWR2cGx5ci9hdWRpb2Jvb2tzaGVsZjoyLjM0LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9BVURJT0JPT0tTSEVMRl84MAogICAgICAtICdUWj0ke1RJTUVaT05FOi1BbWVyaWNhL1Rvcm9udG99JwogICAgdm9sdW1lczoKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtYXVkaW9ib29rczovYXVkaW9ib29rcycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtcG9kY2FzdHM6L3BvZGNhc3RzJwogICAgICAtICdhdWRpb2Jvb2tzaGVsZi1jb25maWc6L2NvbmZpZycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtbWV0YWRhdGE6L21ldGFkYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC0tcXVpZXQgLS10cmllcz0xIC0tdGltZW91dD01IGh0dHA6Ly9sb2NhbGhvc3Q6ODAvcGluZyAtTyAvZGV2L251bGwgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgICAgIHN0YXJ0X3BlcmlvZDogMTVzCg==", "tags": [ "audiobooks", "ebooks", @@ -654,6 +654,18 @@ "minversion": "0.0.0", "port": "8978" }, + "cloudflare-ddns": { + "documentation": "https://github.com/favonia/cloudflare-ddns?utm_source=coolify.io", + "slogan": "A small, feature-rich, and robust Cloudflare DDNS updater.", + "compose": "c2VydmljZXM6CiAgY2xvdWRmbGFyZS1kZG5zOgogICAgaW1hZ2U6ICdmYXZvbmlhL2Nsb3VkZmxhcmUtZGRuczoxLjE2LjInCiAgICBuZXR3b3JrX21vZGU6IGhvc3QKICAgIHVzZXI6ICcxMDAwOjEwMDAnCiAgICByZWFkX29ubHk6IHRydWUKICAgIGNhcF9kcm9wOgogICAgICAtIGFsbAogICAgc2VjdXJpdHlfb3B0OgogICAgICAtICduby1uZXctcHJpdmlsZWdlczp0cnVlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMT1VERkxBUkVfQVBJX1RPS0VOPSR7Q0xPVURGTEFSRV9BUElfVE9LRU46P30nCiAgICAgIC0gJ0RPTUFJTlM9JHtET01BSU5TOj99JwogICAgICAtICdQUk9YSUVEPSR7UFJPWElFRDotZmFsc2V9JwogICAgICAtICdJUDZfUFJPVklERVI9JHtJUDZfUFJPVklERVI6LW5vbmV9Jwo=", + "tags": [ + "cloud", + "ddns" + ], + "category": "automation", + "logo": "svgs/cloudflare-ddns.svg", + "minversion": "0.0.0" + }, "cloudflared": { "documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/?utm_source=coolify.io", "slogan": "Client for Cloudflare Tunnel, a daemon that exposes private services through the Cloudflare edge.", @@ -1149,6 +1161,23 @@ "minversion": "0.0.0", "port": "6555" }, + "emqx-enterprise": { + "documentation": "https://www.emqx.io/docs/en/latest/deploy/install-docker.html?utm_source=coolify.io", + "slogan": "Open-source MQTT broker for IoT, IIoT, and connected vehicles.", + "compose": "c2VydmljZXM6CiAgZW1xeDoKICAgIGltYWdlOiAnZW1xeC9lbXF4LWVudGVycHJpc2U6Ni4yLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9FTVFYXzE4MDgzCiAgICAgIC0gJ0VNUVhfREFTSEJPQVJEX19ERUZBVUxUX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9FTVFYfScKICAgIHBvcnRzOgogICAgICAtICcxODgzOjE4ODMnCiAgICAgIC0gJzgwODM6ODA4MycKICAgICAgLSAnODA4NDo4MDg0JwogICAgICAtICc4ODgzOjg4ODMnCiAgICB2b2x1bWVzOgogICAgICAtICdlbXF4X2RhdGE6L29wdC9lbXF4L2RhdGEnCiAgICAgIC0gJ2VtcXhfbG9nOi9vcHQvZW1xeC9sb2cnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL29wdC9lbXF4L2Jpbi9lbXF4CiAgICAgICAgLSBjdGwKICAgICAgICAtIHN0YXR1cwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDMwcwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogMzBzCg==", + "tags": [ + "mqtt", + "broker", + "iot", + "messaging", + "emqx", + "iiot" + ], + "category": "Networking", + "logo": "svgs/emqx-enterprise.svg", + "minversion": "0.0.0", + "port": "18083" + }, "ente-photos-with-s3": { "documentation": "https://help.ente.io/self-hosting/installation/compose?utm_source=coolify.io", "slogan": "Ente Photos is a fully open source, End to End Encrypted alternative to Google Photos and Apple Photos.", @@ -1637,7 +1666,7 @@ "gitea-runner": { "documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io", "slogan": "Gitea Actions runner for docker", - "compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC4wJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC42JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", "tags": [ "gitea", "actions", @@ -1951,7 +1980,7 @@ "grocy": { "documentation": "https://github.com/grocy/grocy?utm_source=coolify.io", "slogan": "Grocy is a web-based household management and grocery list application.", - "compose": "c2VydmljZXM6CiAgZ3JvY3k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZ3JvY3k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfR1JPQ1kKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICB2b2x1bWVzOgogICAgICAtICdncm9jeS1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "compose": "c2VydmljZXM6CiAgZ3JvY3k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZ3JvY3k6NC42LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9HUk9DWQogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtIFRaPUV1cm9wZS9NYWRyaWQKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dyb2N5LWNvbmZpZzovY29uZmlnJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", "tags": [ "groceries", "household", @@ -1992,6 +2021,25 @@ "logo": "svgs/heimdall.svg", "minversion": "0.0.0" }, + "hermes-agent-with-webui": { + "documentation": "https://github.com/nesquena/hermes-webui?utm_source=coolify.io", + "slogan": "Hermes Agent \u2014 autonomous AI agent with persistent memory, scheduling, and a self-hosted web chat UI.", + "compose": "c2VydmljZXM6CiAgaGVybWVzLWFnZW50OgogICAgaW1hZ2U6ICdub3VzcmVzZWFyY2gvaGVybWVzLWFnZW50OnNoYS0yNzNmZjVjNGE0N2FmNDQ5OWJiZTVlM2IxMTM5ZWZkMzEzOTk1NTU0JwogICAgY29tbWFuZDogJ2dhdGV3YXkgcnVuJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEVSTUVTX0hPTUU9L2hvbWUvaGVybWVzLy5oZXJtZXMKICAgICAgLSBIRVJNRVNfVUlEPTEwMDAKICAgICAgLSBIRVJNRVNfR0lEPTEwMDAKICAgICAgLSAnT1BFTlJPVVRFUl9BUElfS0VZPSR7T1BFTlJPVVRFUl9BUElfS0VZfScKICAgICAgLSAnQU5USFJPUElDX0FQSV9LRVk9JHtBTlRIUk9QSUNfQVBJX0tFWX0nCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogICAgICAtICdHT09HTEVfQVBJX0tFWT0ke0dPT0dMRV9BUElfS0VZfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hlcm1lcy1ob21lOi9ob21lL2hlcm1lcy8uaGVybWVzJwogICAgICAtICdoZXJtZXMtYWdlbnQtc3JjOi9vcHQvaGVybWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd0ZXN0IC1kIC9ob21lL2hlcm1lcy8uaGVybWVzIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgaGVybWVzLXdlYnVpOgogICAgaW1hZ2U6ICdnaGNyLmlvL25lc3F1ZW5hL2hlcm1lcy13ZWJ1aTowLjUxLjkyJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBoZXJtZXMtYWdlbnQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0hFUk1FU1dFQlVJXzg3ODcKICAgICAgLSBIRVJNRVNfV0VCVUlfSE9TVD0wLjAuMC4wCiAgICAgIC0gSEVSTUVTX1dFQlVJX1BPUlQ9ODc4NwogICAgICAtIEhFUk1FU19XRUJVSV9TVEFURV9ESVI9L2hvbWUvaGVybWVzd2VidWkvLmhlcm1lcy93ZWJ1aQogICAgICAtIFdBTlRFRF9VSUQ9MTAwMAogICAgICAtIFdBTlRFRF9HSUQ9MTAwMAogICAgICAtICdIRVJNRVNfV0VCVUlfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX0hFUk1FU1dFQlVJfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hlcm1lcy1ob21lOi9ob21lL2hlcm1lc3dlYnVpLy5oZXJtZXMnCiAgICAgIC0gJ2hlcm1lcy1hZ2VudC1zcmM6L2hvbWUvaGVybWVzd2VidWkvLmhlcm1lcy9oZXJtZXMtYWdlbnQ6cm8nCiAgICAgIC0gJ2hlcm1lcy13b3Jrc3BhY2U6L3dvcmtzcGFjZScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4Nzg3L2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiAzCg==", + "tags": [ + "ai", + "agent", + "llm", + "chatbot", + "hermes", + "openrouter", + "anthropic", + "openai" + ], + "category": "ai", + "logo": "svgs/hermes-agent.png", + "minversion": "0.0.0", + "port": "8787" + }, "heyform": { "documentation": "https://docs.heyform.net/open-source/self-hosting?utm_source=coolify.io", "slogan": "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", @@ -2176,7 +2224,7 @@ "jellyfin": { "documentation": "https://jellyfin.org?utm_source=coolify.io", "slogan": "Jellyfin is a media server for hosting and streaming your media collection.", - "compose": "c2VydmljZXM6CiAgamVsbHlmaW46CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvamVsbHlmaW46bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfSkVMTFlGSU5fODA5NgogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtIFRaPUV1cm9wZS9NYWRyaWQKICAgICAgLSBKRUxMWUZJTl9QdWJsaXNoZWRTZXJ2ZXJVcmw9JFNFUlZJQ0VfVVJMX0pFTExZRklOCiAgICB2b2x1bWVzOgogICAgICAtICdqZWxseWZpbi1jb25maWc6L2NvbmZpZycKICAgICAgLSAnamVsbHlmaW4tdHZzaG93czovZGF0YS90dnNob3dzJwogICAgICAtICdqZWxseWZpbi1tb3ZpZXM6L2RhdGEvbW92aWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwOTYnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "compose": "c2VydmljZXM6CiAgamVsbHlmaW46CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvamVsbHlmaW46MTAuMTEuOCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX0pFTExZRklOXzgwOTYKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICAgIC0gSkVMTFlGSU5fUHVibGlzaGVkU2VydmVyVXJsPSRTRVJWSUNFX1VSTF9KRUxMWUZJTgogICAgdm9sdW1lczoKICAgICAgLSAnamVsbHlmaW4tY29uZmlnOi9jb25maWcnCiAgICAgIC0gJ2plbGx5ZmluLXR2c2hvd3M6L2RhdGEvdHZzaG93cycKICAgICAgLSAnamVsbHlmaW4tbW92aWVzOi9kYXRhL21vdmllcycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDk2JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", "tags": [ "media", "server", @@ -2755,7 +2803,7 @@ "mealie": { "documentation": "https://docs.mealie.io/?utm_source=coolify.io", "slogan": "A recipe manager and meal planner.", - "compose": "c2VydmljZXM6CiAgbWVhbGllOgogICAgaW1hZ2U6ICdnaGNyLmlvL21lYWxpZS1yZWNpcGVzL21lYWxpZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9NRUFMSUVfOTAwMAogICAgICAtICdBTExPV19TSUdOVVA9JHtBTExPV19TSUdOVVA6LXRydWV9JwogICAgICAtICdQVUlEPSR7UFVJRDotMTAwMH0nCiAgICAgIC0gJ1BHSUQ9JHtQR0lEOi0xMDAwfScKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0Jlcmxpbn0nCiAgICAgIC0gJ01BWF9XT1JLRVJTPSR7TUFYX1dPUktFUlM6LTF9JwogICAgICAtICdXRUJfQ09OQ1VSUkVOQ1k9JHtXRUJfQ09OQ1VSUkVOQ1k6LTF9JwogICAgdm9sdW1lczoKICAgICAgLSAnbWVhbGllX2RhdGE6L2FwcC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICJiYXNoIC1jICc6PiAvZGV2L3RjcC8xMjcuMC4wLjEvOTAwMCcgfHwgZXhpdCAxIgogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDUK", + "compose": "c2VydmljZXM6CiAgbWVhbGllOgogICAgaW1hZ2U6ICdnaGNyLmlvL21lYWxpZS1yZWNpcGVzL21lYWxpZTozLjE3LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9NRUFMSUVfOTAwMAogICAgICAtICdBTExPV19TSUdOVVA9JHtBTExPV19TSUdOVVA6LXRydWV9JwogICAgICAtICdQVUlEPSR7UFVJRDotMTAwMH0nCiAgICAgIC0gJ1BHSUQ9JHtQR0lEOi0xMDAwfScKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0Jlcmxpbn0nCiAgICAgIC0gJ01BWF9XT1JLRVJTPSR7TUFYX1dPUktFUlM6LTF9JwogICAgICAtICdXRUJfQ09OQ1VSUkVOQ1k9JHtXRUJfQ09OQ1VSUkVOQ1k6LTF9JwogICAgdm9sdW1lczoKICAgICAgLSAnbWVhbGllX2RhdGE6L2FwcC9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICJiYXNoIC1jICc6PiAvZGV2L3RjcC8xMjcuMC4wLjEvOTAwMCcgfHwgZXhpdCAxIgogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDUK", "tags": [ "recipe manager", "meal planner", @@ -3452,6 +3500,27 @@ "minversion": "0.0.0", "port": "8080" }, + "openobserve": { + "documentation": "https://openobserve.ai/docs/?utm_source=coolify.io", + "slogan": "Cloud-native observability platform for logs, metrics, traces, RUM, errors and session replays \u2014 a 140x cheaper alternative to Elasticsearch, Splunk and Datadog.", + "compose": "c2VydmljZXM6CiAgb3Blbm9ic2VydmU6CiAgICBpbWFnZTogJ3B1YmxpYy5lY3IuYXdzL3ppbmNsYWJzL29wZW5vYnNlcnZlOnYwLjkwLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9PUEVOT0JTRVJWRV81MDgwCiAgICAgIC0gWk9fREFUQV9ESVI9L2RhdGEKICAgICAgLSAnWk9fUk9PVF9VU0VSX0VNQUlMPSR7Wk9fUk9PVF9VU0VSX0VNQUlMOi1yb290QGV4YW1wbGUuY29tfScKICAgICAgLSAnWk9fUk9PVF9VU0VSX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9PUEVOT0JTRVJWRX0nCiAgICAgIC0gJ1pPX1RFTEVNRVRSWT0ke1pPX1RFTEVNRVRSWTotZmFsc2V9JwogICAgICAtICdaT19DT09LSUVfU0VDVVJFX09OTFk9JHtaT19DT09LSUVfU0VDVVJFX09OTFk6LXRydWV9JwogICAgdm9sdW1lczoKICAgICAgLSAnb3Blbm9ic2VydmUtZGF0YTovZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvb3Blbm9ic2VydmUKICAgICAgICAtIG5vZGUKICAgICAgICAtIHN0YXR1cwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUKICAgICAgc3RhcnRfcGVyaW9kOiA2MHMK", + "tags": [ + "logs", + "metrics", + "traces", + "observability", + "monitoring", + "opentelemetry", + "otel", + "elasticsearch", + "splunk", + "datadog" + ], + "category": "monitoring", + "logo": "svgs/openobserve.svg", + "minversion": "0.0.0", + "port": "5080" + }, "openpanel": { "documentation": "https://openpanel.dev/docs?utm_source=coolify.io", "slogan": "Open source alternative to Mixpanel and Plausible for product analytics", @@ -4125,7 +4194,7 @@ "ryot": { "documentation": "https://github.com/ignisda/ryot?utm_source=coolify.io", "slogan": "Roll your own tracker! Ryot is a self-hosted platform for tracking various aspects of life such as media consumption, fitness activities, and more.", - "compose": "c2VydmljZXM6CiAgcnlvdDoKICAgIGltYWdlOiAnaWduaXNkYS9yeW90OnY4JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfUllPVF84MDAwCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RCfScKICAgICAgLSAnU0VSVkVSX0FETUlOX0FDQ0VTU19UT0tFTj0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUllPVH0nCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9BbXN0ZXJkYW19JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwMDAvaGVhbHRoJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncnlvdF9wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1yeW90LWRifScKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0Ftc3RlcmRhbX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgcnlvdDoKICAgIGltYWdlOiAnaWduaXNkYS9yeW90OnYxMC4zLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9SWU9UXzgwMDAKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXNxbDo1NDMyLyR7UE9TVEdSRVNfREJ9JwogICAgICAtICdTRVJWRVJfQURNSU5fQUNDRVNTX1RPS0VOPSR7U0VSVklDRV9QQVNTV09SRF82NF9SWU9UfScKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0Ftc3RlcmRhbX0nCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODAwMC9oZWFsdGgnCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdyeW90X3Bvc3RncmVzcWxfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXJ5b3QtZGJ9JwogICAgICAtICdUWj0ke1RaOi1FdXJvcGUvQW1zdGVyZGFtfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "rss", "reader", diff --git a/templates/service-templates.json b/templates/service-templates.json index 206a8cd6e..b07fe0511 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -171,7 +171,7 @@ "audiobookshelf": { "documentation": "https://www.audiobookshelf.org/?utm_source=coolify.io", "slogan": "Self-hosted audiobook, ebook, and podcast server", - "compose": "c2VydmljZXM6CiAgYXVkaW9ib29rc2hlbGY6CiAgICBpbWFnZTogJ2doY3IuaW8vYWR2cGx5ci9hdWRpb2Jvb2tzaGVsZjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVESU9CT09LU0hFTEZfODAKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotQW1lcmljYS9Ub3JvbnRvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLWF1ZGlvYm9va3M6L2F1ZGlvYm9va3MnCiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLXBvZGNhc3RzOi9wb2RjYXN0cycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtY29uZmlnOi9jb25maWcnCiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLW1ldGFkYXRhOi9tZXRhZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtLXF1aWV0IC0tdHJpZXM9MSAtLXRpbWVvdXQ9NSBodHRwOi8vbG9jYWxob3N0OjgwL3BpbmcgLU8gL2Rldi9udWxsIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDE1cwo=", + "compose": "c2VydmljZXM6CiAgYXVkaW9ib29rc2hlbGY6CiAgICBpbWFnZTogJ2doY3IuaW8vYWR2cGx5ci9hdWRpb2Jvb2tzaGVsZjoyLjM0LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVESU9CT09LU0hFTEZfODAKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotQW1lcmljYS9Ub3JvbnRvfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLWF1ZGlvYm9va3M6L2F1ZGlvYm9va3MnCiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLXBvZGNhc3RzOi9wb2RjYXN0cycKICAgICAgLSAnYXVkaW9ib29rc2hlbGYtY29uZmlnOi9jb25maWcnCiAgICAgIC0gJ2F1ZGlvYm9va3NoZWxmLW1ldGFkYXRhOi9tZXRhZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtLXF1aWV0IC0tdHJpZXM9MSAtLXRpbWVvdXQ9NSBodHRwOi8vbG9jYWxob3N0OjgwL3BpbmcgLU8gL2Rldi9udWxsIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDE1cwo=", "tags": [ "audiobooks", "ebooks", @@ -654,6 +654,18 @@ "minversion": "0.0.0", "port": "8978" }, + "cloudflare-ddns": { + "documentation": "https://github.com/favonia/cloudflare-ddns?utm_source=coolify.io", + "slogan": "A small, feature-rich, and robust Cloudflare DDNS updater.", + "compose": "c2VydmljZXM6CiAgY2xvdWRmbGFyZS1kZG5zOgogICAgaW1hZ2U6ICdmYXZvbmlhL2Nsb3VkZmxhcmUtZGRuczoxLjE2LjInCiAgICBuZXR3b3JrX21vZGU6IGhvc3QKICAgIHVzZXI6ICcxMDAwOjEwMDAnCiAgICByZWFkX29ubHk6IHRydWUKICAgIGNhcF9kcm9wOgogICAgICAtIGFsbAogICAgc2VjdXJpdHlfb3B0OgogICAgICAtICduby1uZXctcHJpdmlsZWdlczp0cnVlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMT1VERkxBUkVfQVBJX1RPS0VOPSR7Q0xPVURGTEFSRV9BUElfVE9LRU46P30nCiAgICAgIC0gJ0RPTUFJTlM9JHtET01BSU5TOj99JwogICAgICAtICdQUk9YSUVEPSR7UFJPWElFRDotZmFsc2V9JwogICAgICAtICdJUDZfUFJPVklERVI9JHtJUDZfUFJPVklERVI6LW5vbmV9Jwo=", + "tags": [ + "cloud", + "ddns" + ], + "category": "automation", + "logo": "svgs/cloudflare-ddns.svg", + "minversion": "0.0.0" + }, "cloudflared": { "documentation": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/?utm_source=coolify.io", "slogan": "Client for Cloudflare Tunnel, a daemon that exposes private services through the Cloudflare edge.", @@ -1149,6 +1161,23 @@ "minversion": "0.0.0", "port": "6555" }, + "emqx-enterprise": { + "documentation": "https://www.emqx.io/docs/en/latest/deploy/install-docker.html?utm_source=coolify.io", + "slogan": "Open-source MQTT broker for IoT, IIoT, and connected vehicles.", + "compose": "c2VydmljZXM6CiAgZW1xeDoKICAgIGltYWdlOiAnZW1xeC9lbXF4LWVudGVycHJpc2U6Ni4yLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRU1RWF8xODA4MwogICAgICAtICdFTVFYX0RBU0hCT0FSRF9fREVGQVVMVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfRU1RWH0nCiAgICBwb3J0czoKICAgICAgLSAnMTg4MzoxODgzJwogICAgICAtICc4MDgzOjgwODMnCiAgICAgIC0gJzgwODQ6ODA4NCcKICAgICAgLSAnODg4Mzo4ODgzJwogICAgdm9sdW1lczoKICAgICAgLSAnZW1xeF9kYXRhOi9vcHQvZW1xeC9kYXRhJwogICAgICAtICdlbXF4X2xvZzovb3B0L2VtcXgvbG9nJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9vcHQvZW1xeC9iaW4vZW1xeAogICAgICAgIC0gY3RsCiAgICAgICAgLSBzdGF0dXMKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiAzMHMKICAgICAgcmV0cmllczogNQogICAgICBzdGFydF9wZXJpb2Q6IDMwcwo=", + "tags": [ + "mqtt", + "broker", + "iot", + "messaging", + "emqx", + "iiot" + ], + "category": "Networking", + "logo": "svgs/emqx-enterprise.svg", + "minversion": "0.0.0", + "port": "18083" + }, "ente-photos-with-s3": { "documentation": "https://help.ente.io/self-hosting/installation/compose?utm_source=coolify.io", "slogan": "Ente Photos is a fully open source, End to End Encrypted alternative to Google Photos and Apple Photos.", @@ -1637,7 +1666,7 @@ "gitea-runner": { "documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io", "slogan": "Gitea Actions runner for docker", - "compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC4wJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC42JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", "tags": [ "gitea", "actions", @@ -1951,7 +1980,7 @@ "grocy": { "documentation": "https://github.com/grocy/grocy?utm_source=coolify.io", "slogan": "Grocy is a web-based household management and grocery list application.", - "compose": "c2VydmljZXM6CiAgZ3JvY3k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZ3JvY3k6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0dST0NZCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgdm9sdW1lczoKICAgICAgLSAnZ3JvY3ktY29uZmlnOi9jb25maWcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", + "compose": "c2VydmljZXM6CiAgZ3JvY3k6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZ3JvY3k6NC42LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fR1JPQ1kKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICB2b2x1bWVzOgogICAgICAtICdncm9jeS1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", "tags": [ "groceries", "household", @@ -1992,6 +2021,25 @@ "logo": "svgs/heimdall.svg", "minversion": "0.0.0" }, + "hermes-agent-with-webui": { + "documentation": "https://github.com/nesquena/hermes-webui?utm_source=coolify.io", + "slogan": "Hermes Agent \u2014 autonomous AI agent with persistent memory, scheduling, and a self-hosted web chat UI.", + "compose": "c2VydmljZXM6CiAgaGVybWVzLWFnZW50OgogICAgaW1hZ2U6ICdub3VzcmVzZWFyY2gvaGVybWVzLWFnZW50OnNoYS0yNzNmZjVjNGE0N2FmNDQ5OWJiZTVlM2IxMTM5ZWZkMzEzOTk1NTU0JwogICAgY29tbWFuZDogJ2dhdGV3YXkgcnVuJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gSEVSTUVTX0hPTUU9L2hvbWUvaGVybWVzLy5oZXJtZXMKICAgICAgLSBIRVJNRVNfVUlEPTEwMDAKICAgICAgLSBIRVJNRVNfR0lEPTEwMDAKICAgICAgLSAnT1BFTlJPVVRFUl9BUElfS0VZPSR7T1BFTlJPVVRFUl9BUElfS0VZfScKICAgICAgLSAnQU5USFJPUElDX0FQSV9LRVk9JHtBTlRIUk9QSUNfQVBJX0tFWX0nCiAgICAgIC0gJ09QRU5BSV9BUElfS0VZPSR7T1BFTkFJX0FQSV9LRVl9JwogICAgICAtICdHT09HTEVfQVBJX0tFWT0ke0dPT0dMRV9BUElfS0VZfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2hlcm1lcy1ob21lOi9ob21lL2hlcm1lcy8uaGVybWVzJwogICAgICAtICdoZXJtZXMtYWdlbnQtc3JjOi9vcHQvaGVybWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd0ZXN0IC1kIC9ob21lL2hlcm1lcy8uaGVybWVzIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgaGVybWVzLXdlYnVpOgogICAgaW1hZ2U6ICdnaGNyLmlvL25lc3F1ZW5hL2hlcm1lcy13ZWJ1aTowLjUxLjkyJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBoZXJtZXMtYWdlbnQKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9IRVJNRVNXRUJVSV84Nzg3CiAgICAgIC0gSEVSTUVTX1dFQlVJX0hPU1Q9MC4wLjAuMAogICAgICAtIEhFUk1FU19XRUJVSV9QT1JUPTg3ODcKICAgICAgLSBIRVJNRVNfV0VCVUlfU1RBVEVfRElSPS9ob21lL2hlcm1lc3dlYnVpLy5oZXJtZXMvd2VidWkKICAgICAgLSBXQU5URURfVUlEPTEwMDAKICAgICAgLSBXQU5URURfR0lEPTEwMDAKICAgICAgLSAnSEVSTUVTX1dFQlVJX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9IRVJNRVNXRUJVSX0nCiAgICB2b2x1bWVzOgogICAgICAtICdoZXJtZXMtaG9tZTovaG9tZS9oZXJtZXN3ZWJ1aS8uaGVybWVzJwogICAgICAtICdoZXJtZXMtYWdlbnQtc3JjOi9ob21lL2hlcm1lc3dlYnVpLy5oZXJtZXMvaGVybWVzLWFnZW50OnJvJwogICAgICAtICdoZXJtZXMtd29ya3NwYWNlOi93b3Jrc3BhY2UnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODc4Ny9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogMwo=", + "tags": [ + "ai", + "agent", + "llm", + "chatbot", + "hermes", + "openrouter", + "anthropic", + "openai" + ], + "category": "ai", + "logo": "svgs/hermes-agent.png", + "minversion": "0.0.0", + "port": "8787" + }, "heyform": { "documentation": "https://docs.heyform.net/open-source/self-hosting?utm_source=coolify.io", "slogan": "Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.", @@ -2176,7 +2224,7 @@ "jellyfin": { "documentation": "https://jellyfin.org?utm_source=coolify.io", "slogan": "Jellyfin is a media server for hosting and streaming your media collection.", - "compose": "c2VydmljZXM6CiAgamVsbHlmaW46CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvamVsbHlmaW46bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0pFTExZRklOXzgwOTYKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICAgIC0gSkVMTFlGSU5fUHVibGlzaGVkU2VydmVyVXJsPSRTRVJWSUNFX0ZRRE5fSkVMTFlGSU4KICAgIHZvbHVtZXM6CiAgICAgIC0gJ2plbGx5ZmluLWNvbmZpZzovY29uZmlnJwogICAgICAtICdqZWxseWZpbi10dnNob3dzOi9kYXRhL3R2c2hvd3MnCiAgICAgIC0gJ2plbGx5ZmluLW1vdmllczovZGF0YS9tb3ZpZXMnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly8xMjcuMC4wLjE6ODA5NicKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=", + "compose": "c2VydmljZXM6CiAgamVsbHlmaW46CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvamVsbHlmaW46MTAuMTEuOCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9KRUxMWUZJTl84MDk2CiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXVyb3BlL01hZHJpZAogICAgICAtIEpFTExZRklOX1B1Ymxpc2hlZFNlcnZlclVybD0kU0VSVklDRV9GUUROX0pFTExZRklOCiAgICB2b2x1bWVzOgogICAgICAtICdqZWxseWZpbi1jb25maWc6L2NvbmZpZycKICAgICAgLSAnamVsbHlmaW4tdHZzaG93czovZGF0YS90dnNob3dzJwogICAgICAtICdqZWxseWZpbi1tb3ZpZXM6L2RhdGEvbW92aWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwOTYnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK", "tags": [ "media", "server", @@ -2755,7 +2803,7 @@ "mealie": { "documentation": "https://docs.mealie.io/?utm_source=coolify.io", "slogan": "A recipe manager and meal planner.", - "compose": "c2VydmljZXM6CiAgbWVhbGllOgogICAgaW1hZ2U6ICdnaGNyLmlvL21lYWxpZS1yZWNpcGVzL21lYWxpZTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUVBTElFXzkwMDAKICAgICAgLSAnQUxMT1dfU0lHTlVQPSR7QUxMT1dfU0lHTlVQOi10cnVlfScKICAgICAgLSAnUFVJRD0ke1BVSUQ6LTEwMDB9JwogICAgICAtICdQR0lEPSR7UEdJRDotMTAwMH0nCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9CZXJsaW59JwogICAgICAtICdNQVhfV09SS0VSUz0ke01BWF9XT1JLRVJTOi0xfScKICAgICAgLSAnV0VCX0NPTkNVUlJFTkNZPSR7V0VCX0NPTkNVUlJFTkNZOi0xfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21lYWxpZV9kYXRhOi9hcHAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAiYmFzaCAtYyAnOj4gL2Rldi90Y3AvMTI3LjAuMC4xLzkwMDAnIHx8IGV4aXQgMSIKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiA1Cg==", + "compose": "c2VydmljZXM6CiAgbWVhbGllOgogICAgaW1hZ2U6ICdnaGNyLmlvL21lYWxpZS1yZWNpcGVzL21lYWxpZTozLjE3LjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTUVBTElFXzkwMDAKICAgICAgLSAnQUxMT1dfU0lHTlVQPSR7QUxMT1dfU0lHTlVQOi10cnVlfScKICAgICAgLSAnUFVJRD0ke1BVSUQ6LTEwMDB9JwogICAgICAtICdQR0lEPSR7UEdJRDotMTAwMH0nCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9CZXJsaW59JwogICAgICAtICdNQVhfV09SS0VSUz0ke01BWF9XT1JLRVJTOi0xfScKICAgICAgLSAnV0VCX0NPTkNVUlJFTkNZPSR7V0VCX0NPTkNVUlJFTkNZOi0xfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21lYWxpZV9kYXRhOi9hcHAvZGF0YScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAiYmFzaCAtYyAnOj4gL2Rldi90Y3AvMTI3LjAuMC4xLzkwMDAnIHx8IGV4aXQgMSIKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiA1Cg==", "tags": [ "recipe manager", "meal planner", @@ -3452,6 +3500,27 @@ "minversion": "0.0.0", "port": "8080" }, + "openobserve": { + "documentation": "https://openobserve.ai/docs/?utm_source=coolify.io", + "slogan": "Cloud-native observability platform for logs, metrics, traces, RUM, errors and session replays \u2014 a 140x cheaper alternative to Elasticsearch, Splunk and Datadog.", + "compose": "c2VydmljZXM6CiAgb3Blbm9ic2VydmU6CiAgICBpbWFnZTogJ3B1YmxpYy5lY3IuYXdzL3ppbmNsYWJzL29wZW5vYnNlcnZlOnYwLjkwLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fT1BFTk9CU0VSVkVfNTA4MAogICAgICAtIFpPX0RBVEFfRElSPS9kYXRhCiAgICAgIC0gJ1pPX1JPT1RfVVNFUl9FTUFJTD0ke1pPX1JPT1RfVVNFUl9FTUFJTDotcm9vdEBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ1pPX1JPT1RfVVNFUl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfT1BFTk9CU0VSVkV9JwogICAgICAtICdaT19URUxFTUVUUlk9JHtaT19URUxFTUVUUlk6LWZhbHNlfScKICAgICAgLSAnWk9fQ09PS0lFX1NFQ1VSRV9PTkxZPSR7Wk9fQ09PS0lFX1NFQ1VSRV9PTkxZOi10cnVlfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ29wZW5vYnNlcnZlLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL29wZW5vYnNlcnZlCiAgICAgICAgLSBub2RlCiAgICAgICAgLSBzdGF0dXMKICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CiAgICAgIHN0YXJ0X3BlcmlvZDogNjBzCg==", + "tags": [ + "logs", + "metrics", + "traces", + "observability", + "monitoring", + "opentelemetry", + "otel", + "elasticsearch", + "splunk", + "datadog" + ], + "category": "monitoring", + "logo": "svgs/openobserve.svg", + "minversion": "0.0.0", + "port": "5080" + }, "openpanel": { "documentation": "https://openpanel.dev/docs?utm_source=coolify.io", "slogan": "Open source alternative to Mixpanel and Plausible for product analytics", @@ -4125,7 +4194,7 @@ "ryot": { "documentation": "https://github.com/ignisda/ryot?utm_source=coolify.io", "slogan": "Roll your own tracker! Ryot is a self-hosted platform for tracking various aspects of life such as media consumption, fitness activities, and more.", - "compose": "c2VydmljZXM6CiAgcnlvdDoKICAgIGltYWdlOiAnaWduaXNkYS9yeW90OnY4JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX1JZT1RfODAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlc3FsOjU0MzIvJHtQT1NUR1JFU19EQn0nCiAgICAgIC0gJ1NFUlZFUl9BRE1JTl9BQ0NFU1NfVE9LRU49JHtTRVJWSUNFX1BBU1NXT1JEXzY0X1JZT1R9JwogICAgICAtICdUWj0ke1RaOi1FdXJvcGUvQW1zdGVyZGFtfScKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo4MDAwL2hlYWx0aCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3J5b3RfcG9zdGdyZXNxbF9kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotcnlvdC1kYn0nCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9BbXN0ZXJkYW19JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", + "compose": "c2VydmljZXM6CiAgcnlvdDoKICAgIGltYWdlOiAnaWduaXNkYS9yeW90OnYxMC4zLjAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUllPVF84MDAwCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzcWw6NTQzMi8ke1BPU1RHUkVTX0RCfScKICAgICAgLSAnU0VSVkVSX0FETUlOX0FDQ0VTU19UT0tFTj0ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfUllPVH0nCiAgICAgIC0gJ1RaPSR7VFo6LUV1cm9wZS9BbXN0ZXJkYW19JwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjgwMDAvaGVhbHRoJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncnlvdF9wb3N0Z3Jlc3FsX2RhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTX0RCOi1yeW90LWRifScKICAgICAgLSAnVFo9JHtUWjotRXVyb3BlL0Ftc3RlcmRhbX0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "rss", "reader", diff --git a/tests/Feature/SyncBunnyTest.php b/tests/Feature/SyncBunnyTest.php new file mode 100644 index 000000000..8e2b892c6 --- /dev/null +++ b/tests/Feature/SyncBunnyTest.php @@ -0,0 +1,68 @@ + Http::response([], 200), + ]); + + $binDir = sys_get_temp_dir().'/sync-bunny-bin-'.uniqid(); + $logFile = sys_get_temp_dir().'/sync-bunny-'.uniqid().'.log'; + + mkdir($binDir, 0755, true); + + createFakeSyncBunnyBinary($binDir, 'gh', <<<'SH' +#!/bin/sh +printf 'gh %s\n' "$*" >> "$SYNC_BUNNY_TEST_LOG" +if [ "$1" = "repo" ] && [ "$2" = "clone" ]; then + mkdir -p "$4/json/nightly" + printf '{}' > "$4/json/releases.json" + printf '{}' > "$4/json/nightly/versions.json" +fi +exit 0 +SH); + + createFakeSyncBunnyBinary($binDir, 'git', <<<'SH' +#!/bin/sh +printf 'git %s\n' "$*" >> "$SYNC_BUNNY_TEST_LOG" +if [ "$1" = "status" ]; then + printf 'M %s\n' "$3" +fi +exit 0 +SH); + + $originalPath = getenv('PATH') ?: ''; + putenv("PATH={$binDir}:{$originalPath}"); + putenv("SYNC_BUNNY_TEST_LOG={$logFile}"); + + try { + $this->artisan("sync:bunny {$option} --nightly") + ->expectsConfirmation($confirmation, 'yes') + ->assertExitCode(0); + } finally { + putenv("PATH={$originalPath}"); + putenv('SYNC_BUNNY_TEST_LOG'); + } + + $log = file_get_contents($logFile); + + expect($log) + ->toContain('json/nightly/versions.json') + ->not->toContain('json/versions-nightly.json'); + + if ($syncsReleases) { + expect($log) + ->toContain('json/nightly/releases.json') + ->not->toContain('git add json/releases.json'); + } +})->with([ + 'release sync with releases' => ['--release', 'Are you sure you want to proceed?', true], + 'versions-only github sync' => ['--github-versions', 'Are you sure you want to sync versions.json via GitHub PR?', false], +]); From 8a40c4e348dd87c472c218f0592b1486a09ecb77 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 26 May 2026 11:51:38 +0200 Subject: [PATCH 29/57] chore(sync-bunny): remove GitHub release sync paths Drop the unused GitHub release and version sync options from sync:bunny, leaving the command focused on BunnyCDN template, release, and nightly syncs. Update the nightly test to assert it does not invoke gh or git. --- app/Console/Commands/SyncBunny.php | 781 +---------------------------- tests/Feature/SyncBunnyTest.php | 60 +-- 2 files changed, 27 insertions(+), 814 deletions(-) diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index 50bdfaf1e..d6d77f22e 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -16,7 +16,7 @@ class SyncBunny extends Command * * @var string */ - protected $signature = 'sync:bunny {--templates} {--release} {--github-releases} {--github-versions} {--nightly}'; + protected $signature = 'sync:bunny {--templates} {--release} {--nightly}'; /** * The console command description. @@ -25,651 +25,6 @@ class SyncBunny extends Command */ protected $description = 'Sync files to BunnyCDN'; - /** - * Fetch GitHub releases and sync to GitHub repository - */ - private function syncReleasesToGitHubRepo(): bool - { - $this->info('Fetching releases from GitHub...'); - try { - $response = Http::timeout(30) - ->get('https://api.github.com/repos/coollabsio/coolify/releases', [ - 'per_page' => 30, // Fetch more releases for better changelog - ]); - - if (! $response->successful()) { - $this->error('Failed to fetch releases from GitHub: '.$response->status()); - - return false; - } - - $releases = $response->json(); - $timestamp = time(); - $tmpDir = sys_get_temp_dir().'/coolify-cdn-'.$timestamp; - $branchName = 'update-releases-'.$timestamp; - - // Clone the repository - $this->info('Cloning coolify-cdn repository...'); - $output = []; - exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to clone repository: '.implode("\n", $output)); - - return false; - } - - // Create feature branch - $this->info('Creating feature branch...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to create branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Write releases.json - $this->info('Writing releases.json...'); - $releasesPath = "$tmpDir/json/releases.json"; - $releasesDir = dirname($releasesPath); - - // Ensure directory exists - if (! is_dir($releasesDir)) { - $this->info("Creating directory: $releasesDir"); - if (! mkdir($releasesDir, 0755, true)) { - $this->error("Failed to create directory: $releasesDir"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - } - - $jsonContent = json_encode($releases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - $bytesWritten = file_put_contents($releasesPath, $jsonContent); - - if ($bytesWritten === false) { - $this->error("Failed to write releases.json to: $releasesPath"); - $this->error('Possible reasons: permission denied or disk full.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Stage and commit - $this->info('Committing changes...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git add json/releases.json 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to stage changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - $this->info('Checking for changes...'); - $statusOutput = []; - exec('cd '.escapeshellarg($tmpDir).' && git status --porcelain json/releases.json 2>&1', $statusOutput, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to check repository status: '.implode("\n", $statusOutput)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - if (empty(array_filter($statusOutput))) { - $this->info('Releases are already up to date. No changes to commit.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return true; - } - - $commitMessage = 'Update releases.json with latest releases - '.date('Y-m-d H:i:s'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git commit -m '.escapeshellarg($commitMessage).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to commit changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Push to remote - $this->info('Pushing branch to remote...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to push branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Create pull request - $this->info('Creating pull request...'); - $prTitle = 'Update releases.json - '.date('Y-m-d H:i:s'); - $prBody = 'Automated update of releases.json with latest '.count($releases).' releases from GitHub API'; - $prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1'; - $output = []; - exec($prCommand, $output, $returnCode); - - // Clean up - exec('rm -rf '.escapeshellarg($tmpDir)); - - if ($returnCode !== 0) { - $this->error('Failed to create PR: '.implode("\n", $output)); - - return false; - } - - $this->info('Pull request created successfully!'); - if (! empty($output)) { - $this->info('PR Output: '.implode("\n", $output)); - } - $this->info('Total releases synced: '.count($releases)); - - return true; - } catch (\Throwable $e) { - $this->error('Error syncing releases: '.$e->getMessage()); - - return false; - } - } - - /** - * Sync both releases.json and versions.json to GitHub repository in one PR - */ - private function syncReleasesAndVersionsToGitHubRepo(string $versionsLocation, bool $nightly = false): bool - { - $this->info('Syncing releases.json and versions.json to GitHub repository...'); - try { - // 1. Fetch releases from GitHub API - $this->info('Fetching releases from GitHub API...'); - $response = Http::timeout(30) - ->get('https://api.github.com/repos/coollabsio/coolify/releases', [ - 'per_page' => 30, - ]); - - if (! $response->successful()) { - $this->error('Failed to fetch releases from GitHub: '.$response->status()); - - return false; - } - - $releases = $response->json(); - - // 2. Read versions.json - if (! file_exists($versionsLocation)) { - $this->error("versions.json not found at: $versionsLocation"); - - return false; - } - - $file = file_get_contents($versionsLocation); - $versionsJson = json_decode($file, true); - $actualVersion = data_get($versionsJson, 'coolify.v4.version'); - - $timestamp = time(); - $tmpDir = sys_get_temp_dir().'/coolify-cdn-combined-'.$timestamp; - $branchName = 'update-releases-and-versions-'.$timestamp; - $versionsTargetPath = $nightly ? 'json/nightly/versions.json' : 'json/versions.json'; - $releasesTargetPath = $nightly ? 'json/nightly/releases.json' : 'json/releases.json'; - - // 3. Clone the repository - $this->info('Cloning coolify-cdn repository...'); - $output = []; - exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to clone repository: '.implode("\n", $output)); - - return false; - } - - // 4. Create feature branch - $this->info('Creating feature branch...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to create branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 5. Write releases.json - $this->info('Writing releases.json...'); - $releasesPath = "$tmpDir/$releasesTargetPath"; - $releasesDir = dirname($releasesPath); - - if (! is_dir($releasesDir)) { - if (! mkdir($releasesDir, 0755, true)) { - $this->error("Failed to create directory: $releasesDir"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - } - - $releasesJsonContent = json_encode($releases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - if (file_put_contents($releasesPath, $releasesJsonContent) === false) { - $this->error("Failed to write releases.json to: $releasesPath"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 6. Write versions.json - $this->info('Writing versions.json...'); - $versionsPath = "$tmpDir/$versionsTargetPath"; - $versionsDir = dirname($versionsPath); - - if (! is_dir($versionsDir)) { - if (! mkdir($versionsDir, 0755, true)) { - $this->error("Failed to create directory: $versionsDir"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - } - - $versionsJsonContent = json_encode($versionsJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - if (file_put_contents($versionsPath, $versionsJsonContent) === false) { - $this->error("Failed to write versions.json to: $versionsPath"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 7. Stage both files - $this->info('Staging changes...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git add '.escapeshellarg($releasesTargetPath).' '.escapeshellarg($versionsTargetPath).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to stage changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 8. Check for changes - $this->info('Checking for changes...'); - $statusOutput = []; - exec('cd '.escapeshellarg($tmpDir).' && git status --porcelain 2>&1', $statusOutput, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to check repository status: '.implode("\n", $statusOutput)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - if (empty(array_filter($statusOutput))) { - $this->info('Both files are already up to date. No changes to commit.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return true; - } - - // 9. Commit changes - $envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION'; - $commitMessage = "Update releases.json and $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git commit -m '.escapeshellarg($commitMessage).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to commit changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 10. Push to remote - $this->info('Pushing branch to remote...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to push branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // 11. Create pull request - $this->info('Creating pull request...'); - $prTitle = "Update releases.json and $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s'); - $prBody = "Automated update:\n- releases.json with latest ".count($releases)." releases from GitHub API\n- $envLabel versions.json to version $actualVersion"; - $prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1'; - $output = []; - exec($prCommand, $output, $returnCode); - - // 12. Clean up - exec('rm -rf '.escapeshellarg($tmpDir)); - - if ($returnCode !== 0) { - $this->error('Failed to create PR: '.implode("\n", $output)); - - return false; - } - - $this->info('Pull request created successfully!'); - if (! empty($output)) { - $this->info('PR URL: '.implode("\n", $output)); - } - $this->info("Version synced: $actualVersion"); - $this->info('Total releases synced: '.count($releases)); - - return true; - } catch (\Throwable $e) { - $this->error('Error syncing to GitHub: '.$e->getMessage()); - - return false; - } - } - - /** - * Sync install.sh, docker-compose, and env files to GitHub repository via PR - */ - private function syncFilesToGitHubRepo(array $files, bool $nightly = false): bool - { - $envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION'; - $this->info("Syncing $envLabel files to GitHub repository..."); - try { - $timestamp = time(); - $tmpDir = sys_get_temp_dir().'/coolify-cdn-files-'.$timestamp; - $branchName = 'update-files-'.$timestamp; - - // Clone the repository - $this->info('Cloning coolify-cdn repository...'); - $output = []; - exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to clone repository: '.implode("\n", $output)); - - return false; - } - - // Create feature branch - $this->info('Creating feature branch...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to create branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Copy each file to its target path in the CDN repo - $copiedFiles = []; - foreach ($files as $sourceFile => $targetPath) { - if (! file_exists($sourceFile)) { - $this->warn("Source file not found, skipping: $sourceFile"); - - continue; - } - - $destPath = "$tmpDir/$targetPath"; - $destDir = dirname($destPath); - - if (! is_dir($destDir)) { - if (! mkdir($destDir, 0755, true)) { - $this->error("Failed to create directory: $destDir"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - } - - if (copy($sourceFile, $destPath) === false) { - $this->error("Failed to copy $sourceFile to $destPath"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - $copiedFiles[] = $targetPath; - $this->info("Copied: $targetPath"); - } - - if (empty($copiedFiles)) { - $this->warn('No files were copied. Nothing to commit.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return true; - } - - // Stage all copied files - $this->info('Staging changes...'); - $output = []; - $stageCmd = 'cd '.escapeshellarg($tmpDir).' && git add '.implode(' ', array_map('escapeshellarg', $copiedFiles)).' 2>&1'; - exec($stageCmd, $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to stage changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Check for changes - $this->info('Checking for changes...'); - $statusOutput = []; - exec('cd '.escapeshellarg($tmpDir).' && git status --porcelain 2>&1', $statusOutput, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to check repository status: '.implode("\n", $statusOutput)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - if (empty(array_filter($statusOutput))) { - $this->info('All files are already up to date. No changes to commit.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return true; - } - - // Commit changes - $commitMessage = "Update $envLabel files (install.sh, docker-compose, env) - ".date('Y-m-d H:i:s'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git commit -m '.escapeshellarg($commitMessage).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to commit changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Push to remote - $this->info('Pushing branch to remote...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to push branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Create pull request - $this->info('Creating pull request...'); - $prTitle = "Update $envLabel files - ".date('Y-m-d H:i:s'); - $fileList = implode("\n- ", $copiedFiles); - $prBody = "Automated update of $envLabel files:\n- $fileList"; - $prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1'; - $output = []; - exec($prCommand, $output, $returnCode); - - // Clean up - exec('rm -rf '.escapeshellarg($tmpDir)); - - if ($returnCode !== 0) { - $this->error('Failed to create PR: '.implode("\n", $output)); - - return false; - } - - $this->info('Pull request created successfully!'); - if (! empty($output)) { - $this->info('PR URL: '.implode("\n", $output)); - } - $this->info('Files synced: '.count($copiedFiles)); - - return true; - } catch (\Throwable $e) { - $this->error('Error syncing files to GitHub: '.$e->getMessage()); - - return false; - } - } - - /** - * Sync versions.json to GitHub repository via PR - */ - private function syncVersionsToGitHubRepo(string $versionsLocation, bool $nightly = false): bool - { - $this->info('Syncing versions.json to GitHub repository...'); - try { - if (! file_exists($versionsLocation)) { - $this->error("versions.json not found at: $versionsLocation"); - - return false; - } - - $file = file_get_contents($versionsLocation); - $json = json_decode($file, true); - $actualVersion = data_get($json, 'coolify.v4.version'); - - $timestamp = time(); - $tmpDir = sys_get_temp_dir().'/coolify-cdn-versions-'.$timestamp; - $branchName = 'update-versions-'.$timestamp; - $targetPath = $nightly ? 'json/nightly/versions.json' : 'json/versions.json'; - - // Clone the repository - $this->info('Cloning coolify-cdn repository...'); - exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg($tmpDir).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to clone repository: '.implode("\n", $output)); - - return false; - } - - // Create feature branch - $this->info('Creating feature branch...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git checkout -b '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to create branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Write versions.json - $this->info('Writing versions.json...'); - $versionsPath = "$tmpDir/$targetPath"; - $versionsDir = dirname($versionsPath); - - // Ensure directory exists - if (! is_dir($versionsDir)) { - $this->info("Creating directory: $versionsDir"); - if (! mkdir($versionsDir, 0755, true)) { - $this->error("Failed to create directory: $versionsDir"); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - } - - $jsonContent = json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); - $bytesWritten = file_put_contents($versionsPath, $jsonContent); - - if ($bytesWritten === false) { - $this->error("Failed to write versions.json to: $versionsPath"); - $this->error('Possible reasons: permission denied or disk full.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Stage and commit - $this->info('Committing changes...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git add '.escapeshellarg($targetPath).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to stage changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - $this->info('Checking for changes...'); - $statusOutput = []; - exec('cd '.escapeshellarg($tmpDir).' && git status --porcelain '.escapeshellarg($targetPath).' 2>&1', $statusOutput, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to check repository status: '.implode("\n", $statusOutput)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - if (empty(array_filter($statusOutput))) { - $this->info('versions.json is already up to date. No changes to commit.'); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return true; - } - - $envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION'; - $commitMessage = "Update $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git commit -m '.escapeshellarg($commitMessage).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to commit changes: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Push to remote - $this->info('Pushing branch to remote...'); - $output = []; - exec('cd '.escapeshellarg($tmpDir).' && git push origin '.escapeshellarg($branchName).' 2>&1', $output, $returnCode); - if ($returnCode !== 0) { - $this->error('Failed to push branch: '.implode("\n", $output)); - exec('rm -rf '.escapeshellarg($tmpDir)); - - return false; - } - - // Create pull request - $this->info('Creating pull request...'); - $prTitle = "Update $envLabel versions.json to $actualVersion - ".date('Y-m-d H:i:s'); - $prBody = "Automated update of $envLabel versions.json to version $actualVersion"; - $output = []; - $prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title '.escapeshellarg($prTitle).' --body '.escapeshellarg($prBody).' --base main --head '.escapeshellarg($branchName).' 2>&1'; - exec($prCommand, $output, $returnCode); - - // Clean up - exec('rm -rf '.escapeshellarg($tmpDir)); - - if ($returnCode !== 0) { - $this->error('Failed to create PR: '.implode("\n", $output)); - - return false; - } - - $this->info('Pull request created successfully!'); - if (! empty($output)) { - $this->info('PR URL: '.implode("\n", $output)); - } - $this->info("Version synced: $actualVersion"); - - return true; - } catch (\Throwable $e) { - $this->error('Error syncing versions.json: '.$e->getMessage()); - - return false; - } - } - /** * Execute the console command. */ @@ -678,8 +33,6 @@ class SyncBunny extends Command $that = $this; $only_template = $this->option('templates'); $only_version = $this->option('release'); - $only_github_releases = $this->option('github-releases'); - $only_github_versions = $this->option('github-versions'); $nightly = $this->option('nightly'); $bunny_cdn = 'https://cdn.coollabs.io'; $bunny_cdn_path = 'coolify'; @@ -737,30 +90,11 @@ class SyncBunny extends Command $install_script_location = "$parent_dir/other/nightly/$install_script"; $versions_location = "$parent_dir/other/nightly/$versions"; } - if (! $only_template && ! $only_version && ! $only_github_releases && ! $only_github_versions) { + if (! $only_template && ! $only_version) { $envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION'; - $this->info("About to sync $envLabel files to BunnyCDN and create a GitHub PR for coolify-cdn."); + $this->info("About to sync $envLabel files to BunnyCDN."); $this->newLine(); - // Build file mapping for diff - if ($nightly) { - $fileMapping = [ - $compose_file_location => 'docker/nightly/docker-compose.yml', - $compose_file_prod_location => 'docker/nightly/docker-compose.prod.yml', - $production_env_location => 'environment/nightly/.env.production', - $upgrade_script_location => 'scripts/nightly/upgrade.sh', - $install_script_location => 'scripts/nightly/install.sh', - ]; - } else { - $fileMapping = [ - $compose_file_location => 'docker/docker-compose.yml', - $compose_file_prod_location => 'docker/docker-compose.prod.yml', - $production_env_location => 'environment/.env.production', - $upgrade_script_location => 'scripts/upgrade.sh', - $install_script_location => 'scripts/install.sh', - ]; - } - // BunnyCDN file mapping (local file => CDN URL path) $bunnyFileMapping = [ $compose_file_location => "$bunny_cdn/$bunny_cdn_path/$compose_file", @@ -813,44 +147,6 @@ class SyncBunny extends Command } } - // Diff against GitHub coolify-cdn repo - $this->newLine(); - $this->info('Fetching coolify-cdn repo to compare...'); - $output = []; - exec('gh repo clone coollabsio/coolify-cdn '.escapeshellarg("$diffTmpDir/repo").' -- --depth 1 2>&1', $output, $returnCode); - - if ($returnCode === 0) { - foreach ($fileMapping as $localFile => $cdnPath) { - $remotePath = "$diffTmpDir/repo/$cdnPath"; - if (! file_exists($localFile)) { - continue; - } - if (! file_exists($remotePath)) { - $this->info("NEW on GitHub: $cdnPath (does not exist in coolify-cdn yet)"); - $hasChanges = true; - - continue; - } - - $diffOutput = []; - exec('diff -u '.escapeshellarg($remotePath).' '.escapeshellarg($localFile).' 2>&1', $diffOutput, $diffCode); - if ($diffCode !== 0) { - $hasChanges = true; - $this->newLine(); - $this->info("--- GitHub: $cdnPath"); - $this->info("+++ Local: $cdnPath"); - foreach ($diffOutput as $line) { - if (str_starts_with($line, '---') || str_starts_with($line, '+++')) { - continue; - } - $this->line($line); - } - } - } - } else { - $this->warn('Could not fetch coolify-cdn repo for diff.'); - } - exec('rm -rf '.escapeshellarg($diffTmpDir)); if (! $hasChanges) { @@ -882,9 +178,9 @@ class SyncBunny extends Command return; } elseif ($only_version) { if ($nightly) { - $this->info('About to sync NIGHTLY versions.json to BunnyCDN and create GitHub PR.'); + $this->info('About to sync NIGHTLY versions.json to BunnyCDN.'); } else { - $this->info('About to sync PRODUCTION versions.json to BunnyCDN and create GitHub PR.'); + $this->info('About to sync PRODUCTION versions.json to BunnyCDN.'); } $file = file_get_contents($versions_location); $json = json_decode($file, true); @@ -892,8 +188,7 @@ class SyncBunny extends Command $this->info("Version: {$actual_version}"); $this->info('This will:'); - $this->info(' 1. Sync versions.json to BunnyCDN (deprecated but still supported)'); - $this->info(' 2. Create ONE GitHub PR with both releases.json and versions.json'); + $this->info(' 1. Sync versions.json to BunnyCDN'); $this->newLine(); $confirmed = confirm('Are you sure you want to proceed?'); @@ -901,8 +196,7 @@ class SyncBunny extends Command return; } - // 1. Sync versions.json to BunnyCDN (deprecated but still needed) - $this->info('Step 1/2: Syncing versions.json to BunnyCDN...'); + $this->info('Syncing versions.json to BunnyCDN...'); Http::pool(fn (Pool $pool) => [ $pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), @@ -910,46 +204,8 @@ class SyncBunny extends Command $this->info('✓ versions.json uploaded & purged to BunnyCDN'); $this->newLine(); - // 2. Create GitHub PR with both releases.json and versions.json - $this->info('Step 2/2: Creating GitHub PR with releases.json and versions.json...'); - $githubSuccess = $this->syncReleasesAndVersionsToGitHubRepo($versions_location, $nightly); - if ($githubSuccess) { - $this->info('✓ GitHub PR created successfully with both files'); - } else { - $this->error('✗ Failed to create GitHub PR'); - } - $this->newLine(); - $this->info('=== Summary ==='); $this->info('BunnyCDN sync: ✓ Complete'); - $this->info('GitHub PR: '.($githubSuccess ? '✓ Created (releases.json + versions.json)' : '✗ Failed')); - - return; - } elseif ($only_github_releases) { - $this->info('About to sync GitHub releases to GitHub repository.'); - $confirmed = confirm('Are you sure you want to sync GitHub releases?'); - if (! $confirmed) { - return; - } - - // Sync releases to GitHub repository - $this->syncReleasesToGitHubRepo(); - - return; - } elseif ($only_github_versions) { - $envLabel = $nightly ? 'NIGHTLY' : 'PRODUCTION'; - $file = file_get_contents($versions_location); - $json = json_decode($file, true); - $actual_version = data_get($json, 'coolify.v4.version'); - - $this->info("About to sync $envLabel versions.json ($actual_version) to GitHub repository."); - $confirmed = confirm('Are you sure you want to sync versions.json via GitHub PR?'); - if (! $confirmed) { - return; - } - - // Sync versions.json to GitHub repository - $this->syncVersionsToGitHubRepo($versions_location, $nightly); return; } @@ -971,31 +227,8 @@ class SyncBunny extends Command $this->info('All files uploaded & purged to BunnyCDN.'); $this->newLine(); - // Sync files to GitHub CDN repository via PR - $this->info('Creating GitHub PR for coolify-cdn repository...'); - if ($nightly) { - $files = [ - $compose_file_location => 'docker/nightly/docker-compose.yml', - $compose_file_prod_location => 'docker/nightly/docker-compose.prod.yml', - $production_env_location => 'environment/nightly/.env.production', - $upgrade_script_location => 'scripts/nightly/upgrade.sh', - $install_script_location => 'scripts/nightly/install.sh', - ]; - } else { - $files = [ - $compose_file_location => 'docker/docker-compose.yml', - $compose_file_prod_location => 'docker/docker-compose.prod.yml', - $production_env_location => 'environment/.env.production', - $upgrade_script_location => 'scripts/upgrade.sh', - $install_script_location => 'scripts/install.sh', - ]; - } - - $githubSuccess = $this->syncFilesToGitHubRepo($files, $nightly); - $this->newLine(); $this->info('=== Summary ==='); $this->info('BunnyCDN sync: Complete'); - $this->info('GitHub PR: '.($githubSuccess ? 'Created' : 'Failed')); } catch (\Throwable $e) { $this->error('Error: '.$e->getMessage()); } diff --git a/tests/Feature/SyncBunnyTest.php b/tests/Feature/SyncBunnyTest.php index 8e2b892c6..841bb5b5f 100644 --- a/tests/Feature/SyncBunnyTest.php +++ b/tests/Feature/SyncBunnyTest.php @@ -2,67 +2,47 @@ use Illuminate\Support\Facades\Http; -function createFakeSyncBunnyBinary(string $binDir, string $name, string $contents): void +function createSyncBunnyFailingBinary(string $binDir, string $name): void { - file_put_contents("{$binDir}/{$name}", $contents); + file_put_contents("{$binDir}/{$name}", <<<'SH' +#!/bin/sh +printf '%s %s\n' "$(basename "$0")" "$*" >> "$SYNC_BUNNY_TEST_LOG" +exit 1 +SH); chmod("{$binDir}/{$name}", 0755); } -it('syncs nightly files to the nested nightly json path in the cdn repository', function (string $option, string $confirmation, bool $syncsReleases) { +it('syncs nightly versions to BunnyCDN without creating a GitHub PR', function () { Http::fake([ - 'api.github.com/repos/coollabsio/coolify/releases*' => Http::response([], 200), + 'storage.bunnycdn.com/*' => Http::response([], 201), + 'api.bunny.net/purge*' => Http::response([], 200), ]); $binDir = sys_get_temp_dir().'/sync-bunny-bin-'.uniqid(); $logFile = sys_get_temp_dir().'/sync-bunny-'.uniqid().'.log'; mkdir($binDir, 0755, true); - - createFakeSyncBunnyBinary($binDir, 'gh', <<<'SH' -#!/bin/sh -printf 'gh %s\n' "$*" >> "$SYNC_BUNNY_TEST_LOG" -if [ "$1" = "repo" ] && [ "$2" = "clone" ]; then - mkdir -p "$4/json/nightly" - printf '{}' > "$4/json/releases.json" - printf '{}' > "$4/json/nightly/versions.json" -fi -exit 0 -SH); - - createFakeSyncBunnyBinary($binDir, 'git', <<<'SH' -#!/bin/sh -printf 'git %s\n' "$*" >> "$SYNC_BUNNY_TEST_LOG" -if [ "$1" = "status" ]; then - printf 'M %s\n' "$3" -fi -exit 0 -SH); + createSyncBunnyFailingBinary($binDir, 'gh'); + createSyncBunnyFailingBinary($binDir, 'git'); $originalPath = getenv('PATH') ?: ''; putenv("PATH={$binDir}:{$originalPath}"); putenv("SYNC_BUNNY_TEST_LOG={$logFile}"); try { - $this->artisan("sync:bunny {$option} --nightly") - ->expectsConfirmation($confirmation, 'yes') + $this->artisan('sync:bunny --release --nightly') + ->expectsConfirmation('Are you sure you want to proceed?', 'yes') + ->expectsOutputToContain('BunnyCDN sync: ✓ Complete') + ->doesntExpectOutputToContain('GitHub PR') ->assertExitCode(0); } finally { putenv("PATH={$originalPath}"); putenv('SYNC_BUNNY_TEST_LOG'); } - $log = file_get_contents($logFile); + expect(file_exists($logFile))->toBeFalse(); - expect($log) - ->toContain('json/nightly/versions.json') - ->not->toContain('json/versions-nightly.json'); - - if ($syncsReleases) { - expect($log) - ->toContain('json/nightly/releases.json') - ->not->toContain('git add json/releases.json'); - } -})->with([ - 'release sync with releases' => ['--release', 'Are you sure you want to proceed?', true], - 'versions-only github sync' => ['--github-versions', 'Are you sure you want to sync versions.json via GitHub PR?', false], -]); + Http::assertSent(fn ($request) => $request->url() === 'https://storage.bunnycdn.com/coolcdn/coolify-nightly/versions.json'); + Http::assertSent(fn ($request) => str_starts_with($request->url(), 'https://api.bunny.net/purge') + && $request['url'] === 'https://cdn.coollabs.io/coolify-nightly/versions.json'); +}); From a22a0c027d80774f9d51465613fa0706ceda1f7b Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 26 May 2026 12:03:30 +0200 Subject: [PATCH 30/57] fix(navbar): align upgrade item with collapsed menu Keep the upgrade action visible while collapsed and apply shared menu icon and label classes so its layout matches other navbar items. Also remove extra logout button spacing. --- resources/views/components/navbar.blade.php | 4 ++-- resources/views/livewire/upgrade.blade.php | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 3b21a81d5..433102dcb 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -368,7 +368,7 @@
@if (isInstanceAdmin() && !isCloud()) @persist('upgrade') -
  • +
  • @endpersist @@ -420,7 +420,7 @@
  • @csrf - -