From 07cdb4ddcc523b91b3997a6c7ebb03094ca0743e Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Thu, 22 May 2025 18:03:39 +0200 Subject: [PATCH 01/95] Create opnform.yaml Add opnform.yaml as template to deploy OpnForm app --- templates/compose/opnform.yaml | 232 +++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 templates/compose/opnform.yaml diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml new file mode 100644 index 000000000..1fe9644b6 --- /dev/null +++ b/templates/compose/opnform.yaml @@ -0,0 +1,232 @@ +# documentation: https://docs.opnform.com/introduction +# slogan: OpnForm is an open-source form builder that lets you create beautiful forms and share them anywhere. It's super fast, you don't need to know how to code +# tags: opnform, form, survey, cloud, open-source, self-hosted, docker, no-code, embeddable +# logo: svg/opnform.svg +# port: 80 + +x-shared-env: &shared-api-env + APP_NAME: "OpnForm" + APP_ENV: production + APP_KEY: ${SERVICE_BASE64_APIKEY} + APP_DEBUG: ${APP_DEBUG:-false} + APP_URL: ${SERVICE_FQDN_NGINX} + SELF_HOSTED: ${SELF_HOSTED:-true} + LOG_CHANNEL: errorlog + LOG_LEVEL: ${LOG_LEVEL:-debug} + FILESYSTEM_DRIVER: ${FILESYSTEM_DRIVER:-local} + LOCAL_FILESYSTEM_VISIBILITY: public + CACHE_DRIVER: redis + QUEUE_CONNECTION: redis + SESSION_DRIVER: redis + SESSION_LIFETIME: 120 + MAIL_MAILER: ${MAIL_MAILER:-log} + MAIL_HOST: ${MAIL_HOST} + MAIL_PORT: ${MAIL_PORT} + MAIL_USERNAME: ${MAIL_USERNAME:-your@email.com} + MAIL_PASSWORD: ${MAIL_PASSWORD} + MAIL_ENCRYPTION: ${MAIL_ENCRYPTION} + MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-your@email.com} + MAIL_FROM_NAME: ${MAIL_FROM_NAME:-OpnForm} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-east-1} + AWS_BUCKET: ${AWS_BUCKET} + JWT_TTL: ${JWT_TTL:-1440} + JWT_SECRET: ${SERVICE_PASSWORD_JWTSECRET} + JWT_SKIP_IP_UA_VALIDATION: ${JWT_SKIP_IP_UA_VALIDATION:-true} + OPEN_AI_API_KEY: ${OPEN_AI_API_KEY} + H_CAPTCHA_SITE_KEY: ${H_CAPTCHA_SITE_KEY} + H_CAPTCHA_SECRET_KEY: ${H_CAPTCHA_SECRET_KEY} + RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} + RE_CAPTCHA_SECRET_KEY: ${RE_CAPTCHA_SECRET_KEY} + TELEGRAM_BOT_ID: ${TELEGRAM_BOT_ID} + TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} + SHOW_OFFICIAL_TEMPLATES: ${SHOW_OFFICIAL_TEMPLATES:-true} + REDIS_HOST: redis + REDIS_PASSWORD: ${SERVICE_PASSWORD_64_REDIS} + # Database settings + DB_HOST: postgresql + DB_DATABASE: ${POSTGRESQL_DATABASE:-opnform} + DB_USERNAME: ${SERVICE_USER_POSTGRESQL} + DB_PASSWORD: ${SERVICE_PASSWORD_POSTGRESQL} + DB_CONNECTION: pgsql + # PHP Configuration + PHP_MEMORY_LIMIT: "1G" + PHP_MAX_EXECUTION_TIME: "600" + PHP_UPLOAD_MAX_FILESIZE: "64M" + PHP_POST_MAX_SIZE: "64M" + +services: + opnform-api: + image: jhumanj/opnform-api:latest + volumes: &api-environment-volumes + - api-storage:/usr/share/nginx/html/storage:rw + environment: + # Use the shared environment variables. + <<: *shared-api-env + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan about || exit 1"] + interval: 30s + timeout: 15s + retries: 3 + start_period: 60s + + api-worker: + image: jhumanj/opnform-api:latest + volumes: *api-environment-volumes + environment: + # Use the shared environment variables. + <<: *shared-api-env + command: ["php", "artisan", "queue:work"] + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "pgrep -f 'php artisan queue:work' > /dev/null || exit 1"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 30s + + api-scheduler: + image: jhumanj/opnform-api:latest + volumes: *api-environment-volumes + environment: + # Use the shared environment variables. + <<: *shared-api-env + command: ["php", "artisan", "schedule:work"] + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan app:scheduler-status --mode=check --max-minutes=3 || exit 1"] + interval: 60s + timeout: 30s + retries: 3 + start_period: 70s # Allow time for first scheduled run and cache write + + opnform-ui: + image: jhumanj/opnform-client:latest + environment: + - NUXT_PUBLIC_APP_URL=/ + - NUXT_PUBLIC_API_BASE=/api + - NUXT_PRIVATE_API_BASE=http://nginx/api + - NUXT_PUBLIC_ENV=${NUXT_PUBLIC_ENV:-production} + - NUXT_PUBLIC_H_CAPTCHA_SITE_KEY=${H_CAPTCHA_SITE_KEY} + - NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY=${RE_CAPTCHA_SITE_KEY} + - NUXT_PUBLIC_ROOT_REDIRECT_URL=${NUXT_PUBLIC_ROOT_REDIRECT_URL} + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://opnform-ui:3000/login || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 45s + + postgresql: + image: postgres:16 + volumes: + - opnform-postgresql-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: ${SERVICE_USER_POSTGRESQL} + POSTGRES_PASSWORD: ${SERVICE_PASSWORD_POSTGRESQL} + POSTGRES_DB: ${POSTGRESQL_DATABASE:-opnform} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s + timeout: 20s + retries: 10 + + redis: + image: redis:7 + environment: + REDIS_PASSWORD: ${SERVICE_PASSWORD_64_REDIS} + volumes: + - redis-data:/data + command: ["redis-server", "--requirepass", "${SERVICE_PASSWORD_64_REDIS}"] + healthcheck: + test: ["CMD", "redis-cli", "-a", "${SERVICE_PASSWORD_64_REDIS}", "PING"] + interval: 10s + timeout: 30s + retries: 3 + + # The nginx reverse proxy. + # used for reverse proxying the API service and Web service. + nginx: + image: nginx:latest + volumes: + - type: bind + source: ./nginx/nginx.conf.template + target: /etc/nginx/conf.d/opnform.conf + read_only: true + content: | + map $request_uri $api_uri { + ~^/api(/.*$) $1; + default $request_uri; + } + + server { + listen 80 default_server; + root /usr/share/nginx/html/public; + + access_log /dev/stdout; + error_log /dev/stderr error; + + index index.html index.htm index.php; + + location / { + proxy_http_version 1.1; + proxy_pass http://opnform-ui:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + + location ~/(api|open|local\/temp|forms\/assets)/ { + set $original_uri $uri; + try_files $uri $uri/ /index.php$is_args$args; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass opnform-api:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/index.php; + fastcgi_param REQUEST_URI $api_uri; + } + + # Deny access to . files + location ~ /\. { + deny all; + } + } + environment: + - SERVICE_FQDN_NGINX + depends_on: + - opnform-api + - opnform-ui + healthcheck: + test: ["CMD", "nginx", "-t"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + api-storage: + driver: local + opnform-postgresql-data: + driver: local + redis-data: + driver: local From c4cb0f70367e168d228ca55858bb4f2366ab9419 Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Thu, 22 May 2025 18:08:24 +0200 Subject: [PATCH 02/95] Add opnform logo into svgs directory --- public/svgs/opnform.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 public/svgs/opnform.svg diff --git a/public/svgs/opnform.svg b/public/svgs/opnform.svg new file mode 100644 index 000000000..70562a4bf --- /dev/null +++ b/public/svgs/opnform.svg @@ -0,0 +1 @@ + \ No newline at end of file From 934126778370673472a1907747108d32af1b4cbb Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Fri, 23 May 2025 18:32:26 +0200 Subject: [PATCH 03/95] Update opnform.yaml - split environment variables per usage (api, worker and scheduler) - remove volume anchor - remove volumes definition --- templates/compose/opnform.yaml | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 1fe9644b6..1b658bca9 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -31,17 +31,9 @@ x-shared-env: &shared-api-env AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-east-1} AWS_BUCKET: ${AWS_BUCKET} - JWT_TTL: ${JWT_TTL:-1440} - JWT_SECRET: ${SERVICE_PASSWORD_JWTSECRET} - JWT_SKIP_IP_UA_VALIDATION: ${JWT_SKIP_IP_UA_VALIDATION:-true} OPEN_AI_API_KEY: ${OPEN_AI_API_KEY} - H_CAPTCHA_SITE_KEY: ${H_CAPTCHA_SITE_KEY} - H_CAPTCHA_SECRET_KEY: ${H_CAPTCHA_SECRET_KEY} - RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} - RE_CAPTCHA_SECRET_KEY: ${RE_CAPTCHA_SECRET_KEY} TELEGRAM_BOT_ID: ${TELEGRAM_BOT_ID} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} - SHOW_OFFICIAL_TEMPLATES: ${SHOW_OFFICIAL_TEMPLATES:-true} REDIS_HOST: redis REDIS_PASSWORD: ${SERVICE_PASSWORD_64_REDIS} # Database settings @@ -59,11 +51,19 @@ x-shared-env: &shared-api-env services: opnform-api: image: jhumanj/opnform-api:latest - volumes: &api-environment-volumes - - api-storage:/usr/share/nginx/html/storage:rw + volumes: + - api-storage:/usr/share/nginx/html/storage environment: # Use the shared environment variables. <<: *shared-api-env + JWT_TTL: ${JWT_TTL:-1440} + JWT_SECRET: ${SERVICE_PASSWORD_JWTSECRET} + JWT_SKIP_IP_UA_VALIDATION: ${JWT_SKIP_IP_UA_VALIDATION:-true} + H_CAPTCHA_SITE_KEY: ${H_CAPTCHA_SITE_KEY} + H_CAPTCHA_SECRET_KEY: ${H_CAPTCHA_SECRET_KEY} + RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} + RE_CAPTCHA_SECRET_KEY: ${RE_CAPTCHA_SECRET_KEY} + SHOW_OFFICIAL_TEMPLATES: ${SHOW_OFFICIAL_TEMPLATES:-true} depends_on: postgresql: condition: service_healthy @@ -78,7 +78,8 @@ services: api-worker: image: jhumanj/opnform-api:latest - volumes: *api-environment-volumes + volumes: + - api-storage:/usr/share/nginx/html/storage environment: # Use the shared environment variables. <<: *shared-api-env @@ -97,7 +98,8 @@ services: api-scheduler: image: jhumanj/opnform-api:latest - volumes: *api-environment-volumes + volumes: + - api-storage:/usr/share/nginx/html/storage environment: # Use the shared environment variables. <<: *shared-api-env @@ -222,11 +224,3 @@ services: timeout: 10s retries: 3 start_period: 40s - -volumes: - api-storage: - driver: local - opnform-postgresql-data: - driver: local - redis-data: - driver: local From 1c2374120394a9b0e82ddc63ad8bc8d2c2da02d1 Mon Sep 17 00:00:00 2001 From: Gabriel Peralta Date: Wed, 23 Jul 2025 20:05:35 -0300 Subject: [PATCH 04/95] Create newt-pangolin --- templates/compose/newt-pangolin | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 templates/compose/newt-pangolin diff --git a/templates/compose/newt-pangolin b/templates/compose/newt-pangolin new file mode 100644 index 000000000..1e52330f9 --- /dev/null +++ b/templates/compose/newt-pangolin @@ -0,0 +1,19 @@ +# documentation: https://docs.fossorial.io/Getting%20Started/overview +# slogan: Pangolin tunnels your services to the internet so you can access anything from anywhere. +# tags: wireguard, reverse-proxy, zero-trust-network-access, open source +# logo: svgs/pangolin-logo.png + +services: + newt: + image: fosrl/newt + container_name: newt + restart: unless-stopped + environment: + - 'PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT:-domain.tld}' + - 'NEWT_ID=${NEWT_ID}' + - 'NEWT_SECRET=${NEWT_SECRET}' + healthcheck: + test: ["CMD", "newt", "--version"] + interval: 5s + timeout: 20s + retries: 10 From e16174d96b420f98ff90687d48fabdbe56027fd3 Mon Sep 17 00:00:00 2001 From: Gabriel Peralta Date: Wed, 23 Jul 2025 20:05:58 -0300 Subject: [PATCH 05/95] Add files via upload --- public/svgs/pangolin-logo.png | Bin 0 -> 33548 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/svgs/pangolin-logo.png diff --git a/public/svgs/pangolin-logo.png b/public/svgs/pangolin-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fb7a252d910a39b3f06b63e72d45a77db336ef55 GIT binary patch literal 33548 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 z Date: Mon, 11 Aug 2025 10:22:12 -0300 Subject: [PATCH 06/95] Update and rename newt-pangolin to newt-pangolin.yaml --- templates/compose/{newt-pangolin => newt-pangolin.yaml} | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename templates/compose/{newt-pangolin => newt-pangolin.yaml} (78%) diff --git a/templates/compose/newt-pangolin b/templates/compose/newt-pangolin.yaml similarity index 78% rename from templates/compose/newt-pangolin rename to templates/compose/newt-pangolin.yaml index 1e52330f9..695766887 100644 --- a/templates/compose/newt-pangolin +++ b/templates/compose/newt-pangolin.yaml @@ -5,11 +5,9 @@ services: newt: - image: fosrl/newt - container_name: newt - restart: unless-stopped + image: fosrl/newt:latest environment: - - 'PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT:-domain.tld}' + - 'PANGOLIN_ENDPOINT=$SERVICE_FQDN_PANGOLIN' - 'NEWT_ID=${NEWT_ID}' - 'NEWT_SECRET=${NEWT_SECRET}' healthcheck: From 4ecca2a4792d63e7525026957259ae084e60b435 Mon Sep 17 00:00:00 2001 From: Gabriel Peralta Date: Thu, 11 Sep 2025 09:24:29 -0300 Subject: [PATCH 07/95] Update newt-pangolin.yaml --- templates/compose/newt-pangolin.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/newt-pangolin.yaml b/templates/compose/newt-pangolin.yaml index 695766887..96e8da979 100644 --- a/templates/compose/newt-pangolin.yaml +++ b/templates/compose/newt-pangolin.yaml @@ -1,4 +1,4 @@ -# documentation: https://docs.fossorial.io/Getting%20Started/overview +# documentation: https://docs.digpangolin.com/manage/sites/install-site # slogan: Pangolin tunnels your services to the internet so you can access anything from anywhere. # tags: wireguard, reverse-proxy, zero-trust-network-access, open source # logo: svgs/pangolin-logo.png From c25e4c705798017a1625d8bc5699dd45364bbbd2 Mon Sep 17 00:00:00 2001 From: Gabriel Peralta Date: Sat, 13 Sep 2025 17:31:07 -0300 Subject: [PATCH 08/95] Update newt-pangolin.yaml --- templates/compose/newt-pangolin.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/compose/newt-pangolin.yaml b/templates/compose/newt-pangolin.yaml index 96e8da979..18183303d 100644 --- a/templates/compose/newt-pangolin.yaml +++ b/templates/compose/newt-pangolin.yaml @@ -7,9 +7,9 @@ services: newt: image: fosrl/newt:latest environment: - - 'PANGOLIN_ENDPOINT=$SERVICE_FQDN_PANGOLIN' - - 'NEWT_ID=${NEWT_ID}' - - 'NEWT_SECRET=${NEWT_SECRET}' + - PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT:-https://pangolin.domain.tld} + - NEWT_ID=${NEWT_ID:-2ix2t8xk22ubpfy} + - NEWT_SECRET=${NEWT_SECRET:-nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2} healthcheck: test: ["CMD", "newt", "--version"] interval: 5s From 4363fbd60ccee380626b8a5c9b91c8f951e9d94f Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Fri, 17 Oct 2025 20:16:48 +0200 Subject: [PATCH 09/95] Update Docker images to specific versions --- templates/compose/opnform.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 1b658bca9..c5218f2c4 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -50,7 +50,7 @@ x-shared-env: &shared-api-env services: opnform-api: - image: jhumanj/opnform-api:latest + image: jhumanj/opnform-api:1.9.7 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -77,7 +77,7 @@ services: start_period: 60s api-worker: - image: jhumanj/opnform-api:latest + image: jhumanj/opnform-api:1.9.7 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -97,7 +97,7 @@ services: start_period: 30s api-scheduler: - image: jhumanj/opnform-api:latest + image: jhumanj/opnform-api:1.9.7 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -117,7 +117,7 @@ services: start_period: 70s # Allow time for first scheduled run and cache write opnform-ui: - image: jhumanj/opnform-client:latest + image: jhumanj/opnform-client:1.9.6 environment: - NUXT_PUBLIC_APP_URL=/ - NUXT_PUBLIC_API_BASE=/api @@ -163,7 +163,7 @@ services: # The nginx reverse proxy. # used for reverse proxying the API service and Web service. nginx: - image: nginx:latest + image: nginx:1.29.2 volumes: - type: bind source: ./nginx/nginx.conf.template From c5fad13ab2d9e9acdadf3070ca521ff063bac234 Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Sun, 19 Oct 2025 15:28:08 +0200 Subject: [PATCH 10/95] Upgrade opnform API and UI images to version 1.10.0 --- templates/compose/opnform.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index c5218f2c4..7cf7b4cbb 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -50,7 +50,7 @@ x-shared-env: &shared-api-env services: opnform-api: - image: jhumanj/opnform-api:1.9.7 + image: jhumanj/opnform-api:1.10.0 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -77,7 +77,7 @@ services: start_period: 60s api-worker: - image: jhumanj/opnform-api:1.9.7 + image: jhumanj/opnform-api:1.10.0 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -97,7 +97,7 @@ services: start_period: 30s api-scheduler: - image: jhumanj/opnform-api:1.9.7 + image: jhumanj/opnform-api:1.10.0 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -117,7 +117,7 @@ services: start_period: 70s # Allow time for first scheduled run and cache write opnform-ui: - image: jhumanj/opnform-client:1.9.6 + image: jhumanj/opnform-client:1.10.0 environment: - NUXT_PUBLIC_APP_URL=/ - NUXT_PUBLIC_API_BASE=/api From 9c99937dd0e34011f14b28461c34c0ad9281db1e Mon Sep 17 00:00:00 2001 From: alexbaron-dev Date: Sun, 19 Oct 2025 17:52:12 +0200 Subject: [PATCH 11/95] Update environment variable syntax in opnform.yaml for consistency --- templates/compose/opnform.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 7cf7b4cbb..860b72eca 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -119,13 +119,13 @@ services: opnform-ui: image: jhumanj/opnform-client:1.10.0 environment: - - NUXT_PUBLIC_APP_URL=/ - - NUXT_PUBLIC_API_BASE=/api - - NUXT_PRIVATE_API_BASE=http://nginx/api - - NUXT_PUBLIC_ENV=${NUXT_PUBLIC_ENV:-production} - - NUXT_PUBLIC_H_CAPTCHA_SITE_KEY=${H_CAPTCHA_SITE_KEY} - - NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY=${RE_CAPTCHA_SITE_KEY} - - NUXT_PUBLIC_ROOT_REDIRECT_URL=${NUXT_PUBLIC_ROOT_REDIRECT_URL} + NUXT_PUBLIC_APP_URL: ${NUXT_PUBLIC_APP_URL:-/} + NUXT_PUBLIC_API_BASE: ${NUXT_PUBLIC_API_BASE:-/api} + NUXT_PRIVATE_API_BASE: ${NUXT_PRIVATE_API_BASE:-http://nginx/api} + NUXT_PUBLIC_ENV: ${NUXT_PUBLIC_ENV:-production} + NUXT_PUBLIC_H_CAPTCHA_SITE_KEY: ${H_CAPTCHA_SITE_KEY} + NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} + NUXT_PUBLIC_ROOT_REDIRECT_URL: ${NUXT_PUBLIC_ROOT_REDIRECT_URL} healthcheck: test: ["CMD-SHELL", "wget --spider -q http://opnform-ui:3000/login || exit 1"] interval: 30s From e430c551306874f26e3c8c436c58a2df350977df Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:21:41 +0100 Subject: [PATCH 12/95] Create si.json Added slovenian language. Do i need to do anything else? --- lang/si.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lang/si.json diff --git a/lang/si.json b/lang/si.json new file mode 100644 index 000000000..b674c4140 --- /dev/null +++ b/lang/si.json @@ -0,0 +1,44 @@ +{ + "auth.login": "Prijava", + "auth.login.authentik": "Prijava z Authentik", + "auth.login.azure": "Prijava z Microsoft", + "auth.login.bitbucket": "Prijava z Bitbucket", + "auth.login.clerk": "Prijava z Clerk", + "auth.login.discord": "Prijava z Discord", + "auth.login.github": "Prijava z GitHub", + "auth.login.gitlab": "Prijava z GitLab", + "auth.login.google": "Prijava z Google", + "auth.login.infomaniak": "Prijava z Infomaniak", + "auth.login.zitadel": "Prijava z Zitadel", + "auth.already_registered": "Ste že registrirani?", + "auth.confirm_password": "Potrdite geslo", + "auth.forgot_password_link": "Ste pozabili geslo?", + "auth.forgot_password_heading": "Obnovitev gesla", + "auth.forgot_password_send_email": "Pošlji e-pošto za ponastavitev gesla", + "auth.register_now": "Registracija", + "auth.logout": "Odjava", + "auth.register": "Registracija", + "auth.registration_disabled": "Registracija je onemogočena. Obrnite se na administratorja.", + "auth.reset_password": "Ponastavi geslo", + "auth.failed": "Ti podatki se ne ujemajo z našimi zapisi.", + "auth.failed.callback": "Obdelava povratnega klica ponudnika prijave ni uspela.", + "auth.failed.password": "Vneseno geslo je nepravilno.", + "auth.failed.email": "Če račun s tem e-poštnim naslovom obstaja, boste kmalu prejeli povezavo za ponastavitev gesla.", + "auth.throttle": "Preveč poskusov prijave. Poskusite znova čez :seconds sekund.", + "input.name": "Ime", + "input.email": "E-pošta", + "input.password": "Geslo", + "input.password.again": "Geslo znova", + "input.code": "Enkratna koda", + "input.recovery_code": "Koda za obnovitev", + "button.save": "Shrani", + "repository.url": "Primeri
Za javne repozitorije uporabite https://....
Za zasebne repozitorije uporabite git@....

https://github.com/coollabsio/coolify-examples bo izbral vejo main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify bo izbral vejo nodejs-fastify.
https://gitea.com/sedlav/expressjs.git bo izbral vejo main.
https://gitlab.com/andrasbacsai/nodejs-example.git bo izbral vejo main.", + "service.stop": "Ta storitev bo ustavljena.", + "resource.docker_cleanup": "Zaženi čiščenje Dockerja (odstrani neuporabljene slike in predpomnilnik gradnje).", + "resource.non_persistent": "Vsi nepersistenčni podatki bodo izbrisani.", + "resource.delete_volumes": "Trajno izbriši vse volumne, povezane s tem virom.", + "resource.delete_connected_networks": "Trajno izbriši vse neprafiniirane omrežja, povezana s tem virom.", + "resource.delete_configurations": "Trajno izbriši vse konfiguracijske datoteke s strežnika.", + "database.delete_backups_locally": "Vse varnostne kopije bodo trajno izbrisane iz lokalnega shranjevanja.", + "warning.sslipdomain": "Vaša konfiguracija je shranjena, vendar domena sslip s https NI priporočljiva, saj so strežniki Let's Encrypt s to javno domeno omejeni (preverjanje SSL certifikata bo spodletelo).

Namesto tega uporabite svojo domeno." +} From dd0575a1ac895545dbba3d2dc24f34c6aa9462eb Mon Sep 17 00:00:00 2001 From: JhumanJ Date: Fri, 31 Oct 2025 17:40:04 +0100 Subject: [PATCH 13/95] Update opnform.yaml to use version 1.10.1 for API and UI images, and correct APP_URL environment variable reference --- templates/compose/opnform.yaml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 860b72eca..7e311e5a6 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -9,7 +9,7 @@ x-shared-env: &shared-api-env APP_ENV: production APP_KEY: ${SERVICE_BASE64_APIKEY} APP_DEBUG: ${APP_DEBUG:-false} - APP_URL: ${SERVICE_FQDN_NGINX} + APP_URL: ${SERVICE_FQDN_OPNFORM} SELF_HOSTED: ${SELF_HOSTED:-true} LOG_CHANNEL: errorlog LOG_LEVEL: ${LOG_LEVEL:-debug} @@ -50,7 +50,7 @@ x-shared-env: &shared-api-env services: opnform-api: - image: jhumanj/opnform-api:1.10.0 + image: jhumanj/opnform-api:1.10.1 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -77,7 +77,7 @@ services: start_period: 60s api-worker: - image: jhumanj/opnform-api:1.10.0 + image: jhumanj/opnform-api:1.10.1 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -90,14 +90,15 @@ services: redis: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "pgrep -f 'php artisan queue:work' > /dev/null || exit 1"] + test: + ["CMD-SHELL", "pgrep -f 'php artisan queue:work' > /dev/null || exit 1"] interval: 60s timeout: 10s retries: 3 start_period: 30s - + api-scheduler: - image: jhumanj/opnform-api:1.10.0 + image: jhumanj/opnform-api:1.10.1 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -110,14 +111,18 @@ services: redis: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan app:scheduler-status --mode=check --max-minutes=3 || exit 1"] + test: + [ + "CMD-SHELL", + "php /usr/share/nginx/html/artisan app:scheduler-status --mode=check --max-minutes=3 || exit 1", + ] interval: 60s timeout: 30s retries: 3 start_period: 70s # Allow time for first scheduled run and cache write opnform-ui: - image: jhumanj/opnform-client:1.10.0 + image: jhumanj/opnform-client:1.10.1 environment: NUXT_PUBLIC_APP_URL: ${NUXT_PUBLIC_APP_URL:-/} NUXT_PUBLIC_API_BASE: ${NUXT_PUBLIC_API_BASE:-/api} @@ -127,7 +132,8 @@ services: NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} NUXT_PUBLIC_ROOT_REDIRECT_URL: ${NUXT_PUBLIC_ROOT_REDIRECT_URL} healthcheck: - test: ["CMD-SHELL", "wget --spider -q http://opnform-ui:3000/login || exit 1"] + test: + ["CMD-SHELL", "wget --spider -q http://opnform-ui:3000/login || exit 1"] interval: 30s timeout: 10s retries: 3 @@ -214,7 +220,7 @@ services: } } environment: - - SERVICE_FQDN_NGINX + - SERVICE_FQDN_OPNFORM depends_on: - opnform-api - opnform-ui From 0aac7aa7996ff36f754ac67ac5396a26ac681131 Mon Sep 17 00:00:00 2001 From: Daren Tan Date: Mon, 3 Nov 2025 21:29:53 +0800 Subject: [PATCH 14/95] fix: codimd docker-compose domain --- templates/compose/codimd.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/codimd.yaml b/templates/compose/codimd.yaml index 39d44258a..fe947898f 100644 --- a/templates/compose/codimd.yaml +++ b/templates/compose/codimd.yaml @@ -10,8 +10,8 @@ services: image: nabo.codimd.dev/hackmdio/hackmd:latest environment: - SERVICE_URL_CODIMD_3000 - - CMD_DOMAIN=${SERVICE_URL_CODIMD} - - CMD_PROTOCOL_USESSL=${CMD_PROTOCOL_USESSL:-false} + - CMD_DOMAIN=${SERVICE_FQDN_CODIMD} + - CMD_PROTOCOL_USESSL=${CMD_PROTOCOL_USESSL:-true} - CMD_SESSION_SECRET=${SERVICE_PASSWORD_SESSIONSECRET} - CMD_USECDN=${CMD_USECDN:-false} - CMD_DB_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-codimd-db} From f31ba424d5cbbc371a40e70d671091543673ed13 Mon Sep 17 00:00:00 2001 From: Diogo Carvalho Date: Thu, 6 Nov 2025 10:55:01 +0000 Subject: [PATCH 15/95] Update mosquitto.yaml Fix spacing in conditional check for service user --- templates/compose/mosquitto.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/mosquitto.yaml b/templates/compose/mosquitto.yaml index 4c417e746..e52eca84a 100644 --- a/templates/compose/mosquitto.yaml +++ b/templates/compose/mosquitto.yaml @@ -34,7 +34,7 @@ services: fi && echo ''require_certificate ''$REQUIRE_CERTIFICATE >> /mosquitto/config/mosquitto.conf && echo ''allow_anonymous ''$ALLOW_ANONYMOUS >> /mosquitto/config/mosquitto.conf; - if [ -n ''$SERVICE_USER_MOSQUITTO''] && [ -n ''$SERVICE_PASSWORD_MOSQUITTO'' ]; then + if [ -n ''$SERVICE_USER_MOSQUITTO'' ] && [ -n ''$SERVICE_PASSWORD_MOSQUITTO'' ]; then echo ''password_file /mosquitto/config/passwords'' >> /mosquitto/config/mosquitto.conf && touch /mosquitto/config/passwords && chmod 0700 /mosquitto/config/passwords && From 69b8abde634a38741f592156d27d54cf52de5a23 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 15:01:48 +0530 Subject: [PATCH 16/95] Fix(Documenso): Resolve pending status issue for Documenso deployments (fixes #1767) --- templates/compose/documenso.yaml | 42 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 8536945ab..4fc5dd0a9 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -28,8 +28,9 @@ services: - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/apps/remix/certs/certificate.p12 - - NEXT_PRIVATE_SIGNING_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} + - NEXT_PRIVATE_SIGNING_TRANSPORT=local-file + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/certs/cert.p12 + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-DO} - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-Santiago} @@ -49,10 +50,14 @@ services: - /bin/sh - -c - | - echo "./certs" > /tmp/certs_dir_path - echo "./make-certs.sh" > /tmp/cert_script_path - echo "${SERVICE_PASSWORD_DOCUMENSO}" > /tmp/cert_pass - + CERT_DIR="/app/certs" + CERT_PASSPHRASE="${SERVICE_PASSWORD_DOCUMENSO}" + + # Save original working directory + ORIGINAL_DIR="$(pwd)" + + mkdir -p "$CERT_DIR" + touch /tmp/cert_info_path cat < /tmp/cert_info_path [ req ] @@ -68,11 +73,10 @@ services: emailAddress = ${CERT_INFO_EMAIL} EOF - cat < "$(cat /tmp/cert_script_path)" - mkdir -p "$(cat /tmp/certs_dir_path)" && cd "$(cat /tmp/certs_dir_path)" - + cd "$CERT_DIR" + openssl genrsa -out private.key 2048 - + openssl req \ -new \ -x509 \ @@ -80,19 +84,21 @@ services: -out certificate.crt \ -days ${CERT_VALID_DAYS} \ -config /tmp/cert_info_path - + openssl pkcs12 \ -export \ - -out certificate.p12 \ + -out cert.p12 \ -inkey private.key \ -in certificate.crt \ -legacy \ - -password file:/tmp/cert_pass - EOF - chmod +x "$(cat /tmp/cert_script_path)" - - sh "$(cat /tmp/cert_script_path)" - + -passout pass:"$CERT_PASSPHRASE" + + chown 1001:1001 cert.p12 private.key certificate.crt + chmod 400 cert.p12 private.key certificate.crt + + # Return to original directory before starting application + cd "$ORIGINAL_DIR" + ./start.sh database: From 08eb6ff98144bb6ef89f45789da2203798b3bea9 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 15:10:04 +0530 Subject: [PATCH 17/95] Fix(Documenso): Resolve pending status issue for Documenso deployments (fixes #1767) --- templates/compose/documenso.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 4fc5dd0a9..e51c0e8be 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -28,7 +28,7 @@ services: - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - - NEXT_PRIVATE_SIGNING_TRANSPORT=local-file + - NEXT_PRIVATE_SIGNING_TRANSPORT=local - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/certs/cert.p12 - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} From 50accfeb2a1368ab067dc8745c78f93fa0ce77c2 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 16:45:16 +0530 Subject: [PATCH 18/95] fix: updated passout key --- templates/compose/documenso.yaml | 128 +++++++++++++++++++------------ 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index e51c0e8be..6ad054240 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -11,52 +11,72 @@ services: depends_on: database: condition: service_healthy + ports: + - "3000:3000" environment: - - SERVICE_URL_DOCUMENSO_3000 - - NEXTAUTH_URL=${SERVICE_URL_DOCUMENSO} - - NEXTAUTH_SECRET=${SERVICE_BASE64_AUTHSECRET} - - NEXT_PRIVATE_ENCRYPTION_KEY=${SERVICE_BASE64_ENCRYPTIONKEY} - - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${SERVICE_BASE64_SECONDARYENCRYPTIONKEY} - - NEXT_PUBLIC_WEBAPP_URL=${SERVICE_URL_DOCUMENSO} - - NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY} - - NEXT_PRIVATE_SMTP_TRANSPORT=${NEXT_PRIVATE_SMTP_TRANSPORT} - - NEXT_PRIVATE_SMTP_HOST=${NEXT_PRIVATE_SMTP_HOST} - - NEXT_PRIVATE_SMTP_PORT=${NEXT_PRIVATE_SMTP_PORT} - - NEXT_PRIVATE_SMTP_USERNAME=${NEXT_PRIVATE_SMTP_USERNAME} - - NEXT_PRIVATE_SMTP_PASSWORD=${NEXT_PRIVATE_SMTP_PASSWORD} - - NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME} - - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} - - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public - - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - SERVICE_URL_DOCUMENSO_3000=http://localhost:3000 + - NEXTAUTH_URL=http://localhost:3000 + - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-test-secret-key-change-in-production} + - NEXT_PRIVATE_ENCRYPTION_KEY=${NEXT_PRIVATE_ENCRYPTION_KEY:-test-encryption-key-32-chars} + - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY:-test-secondary-encryption-key-64-characters-long-for-production-use} + - NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000 + - NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY:-} + - NEXT_PRIVATE_SMTP_TRANSPORT=${NEXT_PRIVATE_SMTP_TRANSPORT:-} + - NEXT_PRIVATE_SMTP_HOST=${NEXT_PRIVATE_SMTP_HOST:-} + - NEXT_PRIVATE_SMTP_PORT=${NEXT_PRIVATE_SMTP_PORT:-} + - NEXT_PRIVATE_SMTP_USERNAME=${NEXT_PRIVATE_SMTP_USERNAME:-} + - NEXT_PRIVATE_SMTP_PASSWORD=${NEXT_PRIVATE_SMTP_PASSWORD:-} + - NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME:-} + - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS:-} + - NEXT_PRIVATE_DATABASE_URL=postgresql://${POSTGRES_USER:-documenso}:${POSTGRES_PASSWORD:-documenso}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${POSTGRES_USER:-documenso}:${POSTGRES_PASSWORD:-documenso}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_SIGNING_TRANSPORT=local - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/certs/cert.p12 - - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE:-documenso} - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} - - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-DO} - - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-Santiago} - - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-Santiago} - - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Example INC} + - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-US} + - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-State} + - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-City} + - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Test Organization} - CERT_INFO_ORGANIZATIONAL_UNIT=${CERT_INFO_ORGANIZATIONAL_UNIT:-IT Department} - - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@gmail.com} + - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-test@example.com} - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_LOGIN:-false} + - SERVICE_PASSWORD_DOCUMENSO=${SERVICE_PASSWORD_DOCUMENSO:-documenso} + - SERVICE_URL_DOCUMENSO=http://localhost:3000 healthcheck: test: - CMD-SHELL - - "wget -q -O - http://documenso:3000/ | grep -q 'Sign in to your account'" - interval: 2s - timeout: 10s - retries: 20 + - "wget -q -O - http://localhost:3000/ | grep -q 'Sign in to your account' || exit 1" + interval: 10s + timeout: 5s + retries: 10 + start_period: 40s entrypoint: - /bin/sh - -c - | - CERT_DIR="/app/certs" - CERT_PASSPHRASE="${SERVICE_PASSWORD_DOCUMENSO}" + CERT_PASSPHRASE="$${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE}" # Save original working directory - ORIGINAL_DIR="$(pwd)" + ORIGINAL_DIR="$$(pwd)" - mkdir -p "$CERT_DIR" + # Find openssl binary (should be available in v1.12.10+) + OPENSSL_CMD="$$(which openssl 2>/dev/null || command -v openssl 2>/dev/null || echo '/usr/bin/openssl')" + + # Verify openssl is available + if ! $$OPENSSL_CMD version >/dev/null 2>&1; then + echo "Error: OpenSSL not found. Please use Documenso image v1.12.10 or later." + exit 1 + fi + + # Create certificate directory - use /app/certs (writable by user 1001) + CERT_DIR="/app/certs" + mkdir -p "$$CERT_DIR" || { + # Fallback to tmp if app directory not writable + CERT_DIR="/tmp/certs" + mkdir -p "$$CERT_DIR" + echo "Warning: Using fallback directory: $$CERT_DIR" + } touch /tmp/cert_info_path cat < /tmp/cert_info_path @@ -64,53 +84,63 @@ services: distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] - C = ${CERT_INFO_COUNTRY_NAME} - ST = ${CERT_INFO_STATE_OR_PROVIDENCE} - L = ${CERT_INFO_LOCALITY_NAME} - O = ${CERT_INFO_ORGANIZATION_NAME} - OU = ${CERT_INFO_ORGANIZATIONAL_UNIT} - CN = ${SERVICE_URL_DOCUMENSO} - emailAddress = ${CERT_INFO_EMAIL} + C = $${CERT_INFO_COUNTRY_NAME} + ST = $${CERT_INFO_STATE_OR_PROVIDENCE} + L = $${CERT_INFO_LOCALITY_NAME} + O = $${CERT_INFO_ORGANIZATION_NAME} + OU = $${CERT_INFO_ORGANIZATIONAL_UNIT} + CN = $${SERVICE_URL_DOCUMENSO} + emailAddress = $${CERT_INFO_EMAIL} EOF - cd "$CERT_DIR" + cd "$$CERT_DIR" - openssl genrsa -out private.key 2048 + $$OPENSSL_CMD genrsa -out private.key 2048 - openssl req \ + $$OPENSSL_CMD req \ -new \ -x509 \ -key private.key \ -out certificate.crt \ - -days ${CERT_VALID_DAYS} \ + -days $${CERT_VALID_DAYS} \ -config /tmp/cert_info_path - openssl pkcs12 \ + $$OPENSSL_CMD pkcs12 \ -export \ -out cert.p12 \ -inkey private.key \ -in certificate.crt \ -legacy \ - -passout pass:"$CERT_PASSPHRASE" + -passout pass:"$$CERT_PASSPHRASE" - chown 1001:1001 cert.p12 private.key certificate.crt + # Set permissions (may fail if not root, but will work in Coolify) + chown 1001:1001 cert.p12 private.key certificate.crt 2>/dev/null || true chmod 400 cert.p12 private.key certificate.crt + # Update environment variable if directory changed + if [ "$$CERT_DIR" != "/app/certs" ]; then + export NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH="$$CERT_DIR/cert.p12" + fi + # Return to original directory before starting application - cd "$ORIGINAL_DIR" + cd "$$ORIGINAL_DIR" ./start.sh database: image: postgres:17 environment: - - POSTGRES_USER=${SERVICE_USER_POSTGRES} - - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_USER=${POSTGRES_USER:-documenso} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-documenso} - POSTGRES_DB=${POSTGRES_DB:-documenso-db} volumes: - documenso_postgresql_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-documenso} -d ${POSTGRES_DB:-documenso-db}"] interval: 5s - timeout: 20s + timeout: 5s retries: 10 + start_period: 10s + +volumes: + documenso_postgresql_data: \ No newline at end of file From 40eb399b360a818843cd05d9cc9ee91d7370e408 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 16:54:31 +0530 Subject: [PATCH 19/95] fix: updated envs --- templates/compose/documenso.yaml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 6ad054240..76e62fcb4 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -11,8 +11,6 @@ services: depends_on: database: condition: service_healthy - ports: - - "3000:3000" environment: - SERVICE_URL_DOCUMENSO_3000=http://localhost:3000 - NEXTAUTH_URL=http://localhost:3000 @@ -32,17 +30,16 @@ services: - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${POSTGRES_USER:-documenso}:${POSTGRES_PASSWORD:-documenso}@database/${POSTGRES_DB:-documenso-db}?schema=public - NEXT_PRIVATE_SIGNING_TRANSPORT=local - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/certs/cert.p12 - - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE:-documenso} + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-US} - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-State} - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-City} - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Test Organization} - CERT_INFO_ORGANIZATIONAL_UNIT=${CERT_INFO_ORGANIZATIONAL_UNIT:-IT Department} - - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-test@example.com} + - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@example.com} - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_LOGIN:-false} - - SERVICE_PASSWORD_DOCUMENSO=${SERVICE_PASSWORD_DOCUMENSO:-documenso} - - SERVICE_URL_DOCUMENSO=http://localhost:3000 + - SERVICE_PASSWORD_DOCUMENSO=${SERVICE_PASSWORD_DOCUMENSO:-} healthcheck: test: - CMD-SHELL @@ -56,6 +53,7 @@ services: - -c - | CERT_PASSPHRASE="$${NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE}" + PASSPHRASE_FILE="/tmp/cert_passphrase" # Save original working directory ORIGINAL_DIR="$$(pwd)" @@ -78,6 +76,11 @@ services: echo "Warning: Using fallback directory: $$CERT_DIR" } + # Create passphrase file for secure handling (prevents exposure in process list) + # This avoids shell word-splitting issues and prevents passphrase from appearing in ps/process list + echo -n "$$CERT_PASSPHRASE" > "$$PASSPHRASE_FILE" + chmod 600 "$$PASSPHRASE_FILE" + touch /tmp/cert_info_path cat < /tmp/cert_info_path [ req ] @@ -105,13 +108,18 @@ services: -days $${CERT_VALID_DAYS} \ -config /tmp/cert_info_path + # Create P12 certificate using file-based passphrase (prevents exposure in process list) + # Private key is not encrypted, so we only need -passout (not -passin) $$OPENSSL_CMD pkcs12 \ -export \ -out cert.p12 \ -inkey private.key \ -in certificate.crt \ -legacy \ - -passout pass:"$$CERT_PASSPHRASE" + -passout file:"$$PASSPHRASE_FILE" + + # Clean up passphrase file immediately after use + rm -f "$$PASSPHRASE_FILE" # Set permissions (may fail if not root, but will work in Coolify) chown 1001:1001 cert.p12 private.key certificate.crt 2>/dev/null || true From 1cd98f7b5aa347c749def32426b78cd48c6f2de1 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 17:02:09 +0530 Subject: [PATCH 20/95] fix: secure deploy --- templates/compose/documenso.yaml | 60 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 76e62fcb4..f78c04f7f 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -12,32 +12,34 @@ services: database: condition: service_healthy environment: - - SERVICE_URL_DOCUMENSO_3000=http://localhost:3000 - - NEXTAUTH_URL=http://localhost:3000 - - NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-test-secret-key-change-in-production} - - NEXT_PRIVATE_ENCRYPTION_KEY=${NEXT_PRIVATE_ENCRYPTION_KEY:-test-encryption-key-32-chars} - - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY:-test-secondary-encryption-key-64-characters-long-for-production-use} - - NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000 - - NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY:-} - - NEXT_PRIVATE_SMTP_TRANSPORT=${NEXT_PRIVATE_SMTP_TRANSPORT:-} - - NEXT_PRIVATE_SMTP_HOST=${NEXT_PRIVATE_SMTP_HOST:-} - - NEXT_PRIVATE_SMTP_PORT=${NEXT_PRIVATE_SMTP_PORT:-} - - NEXT_PRIVATE_SMTP_USERNAME=${NEXT_PRIVATE_SMTP_USERNAME:-} - - NEXT_PRIVATE_SMTP_PASSWORD=${NEXT_PRIVATE_SMTP_PASSWORD:-} - - NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME:-} - - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS:-} - - NEXT_PRIVATE_DATABASE_URL=postgresql://${POSTGRES_USER:-documenso}:${POSTGRES_PASSWORD:-documenso}@database/${POSTGRES_DB:-documenso-db}?schema=public - - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${POSTGRES_USER:-documenso}:${POSTGRES_PASSWORD:-documenso}@database/${POSTGRES_DB:-documenso-db}?schema=public + - SERVICE_URL_DOCUMENSO_3000 + - NEXTAUTH_URL=${SERVICE_URL_DOCUMENSO} + - NEXTAUTH_SECRET=${SERVICE_BASE64_AUTHSECRET} + - NEXT_PRIVATE_ENCRYPTION_KEY=${SERVICE_BASE64_ENCRYPTIONKEY} + - NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=${SERVICE_BASE64_SECONDARYENCRYPTIONKEY} + - NEXT_PUBLIC_WEBAPP_URL=${SERVICE_URL_DOCUMENSO} + - NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY} + - NEXT_PRIVATE_SMTP_TRANSPORT=${NEXT_PRIVATE_SMTP_TRANSPORT} + - NEXT_PRIVATE_SMTP_HOST=${NEXT_PRIVATE_SMTP_HOST} + - NEXT_PRIVATE_SMTP_PORT=${NEXT_PRIVATE_SMTP_PORT} + - NEXT_PRIVATE_SMTP_USERNAME=${NEXT_PRIVATE_SMTP_USERNAME} + - NEXT_PRIVATE_SMTP_PASSWORD=${NEXT_PRIVATE_SMTP_PASSWORD} + - NEXT_PRIVATE_SMTP_FROM_NAME=${NEXT_PRIVATE_SMTP_FROM_NAME} + - NEXT_PRIVATE_SMTP_FROM_ADDRESS=${NEXT_PRIVATE_SMTP_FROM_ADDRESS} + - NEXT_PRIVATE_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_DIRECT_DATABASE_URL=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@database/${POSTGRES_DB:-documenso-db}?schema=public + - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/apps/remix/certs/certificate.p12 + - NEXT_PRIVATE_SIGNING_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} - NEXT_PRIVATE_SIGNING_TRANSPORT=local - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/app/certs/cert.p12 - NEXT_PRIVATE_SIGNING_LOCAL_FILE_PASSPHRASE=${SERVICE_PASSWORD_DOCUMENSO} - CERT_VALID_DAYS=${CERT_VALID_DAYS:-365} - - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-US} - - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-State} - - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-City} - - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Test Organization} + - CERT_INFO_COUNTRY_NAME=${CERT_INFO_COUNTRY_NAME:-DO} + - CERT_INFO_STATE_OR_PROVIDENCE=${CERT_INFO_STATE_OR_PROVIDENCE:-Santiago} + - CERT_INFO_LOCALITY_NAME=${CERT_INFO_LOCALITY_NAME:-Santiago} + - CERT_INFO_ORGANIZATION_NAME=${CERT_INFO_ORGANIZATION_NAME:-Example INC} - CERT_INFO_ORGANIZATIONAL_UNIT=${CERT_INFO_ORGANIZATIONAL_UNIT:-IT Department} - - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@example.com} + - CERT_INFO_EMAIL=${CERT_INFO_EMAIL:-example@gmail.com} - NEXT_PUBLIC_DISABLE_SIGNUP=${DISABLE_LOGIN:-false} - SERVICE_PASSWORD_DOCUMENSO=${SERVICE_PASSWORD_DOCUMENSO:-} healthcheck: @@ -87,13 +89,13 @@ services: distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] - C = $${CERT_INFO_COUNTRY_NAME} - ST = $${CERT_INFO_STATE_OR_PROVIDENCE} - L = $${CERT_INFO_LOCALITY_NAME} - O = $${CERT_INFO_ORGANIZATION_NAME} - OU = $${CERT_INFO_ORGANIZATIONAL_UNIT} - CN = $${SERVICE_URL_DOCUMENSO} - emailAddress = $${CERT_INFO_EMAIL} + C = ${CERT_INFO_COUNTRY_NAME} + ST = ${CERT_INFO_STATE_OR_PROVIDENCE} + L = ${CERT_INFO_LOCALITY_NAME} + O = ${CERT_INFO_ORGANIZATION_NAME} + OU = ${CERT_INFO_ORGANIZATIONAL_UNIT} + CN = ${SERVICE_URL_DOCUMENSO} + emailAddress = ${CERT_INFO_EMAIL} EOF cd "$$CERT_DIR" @@ -139,7 +141,7 @@ services: image: postgres:17 environment: - POSTGRES_USER=${POSTGRES_USER:-documenso} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-documenso} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-PLACEHOLDER_PASSWORD} - POSTGRES_DB=${POSTGRES_DB:-documenso-db} volumes: - documenso_postgresql_data:/var/lib/postgresql/data From 87a97468c2821a7a6dbebc31a9f515944152ae9a Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 17:03:00 +0530 Subject: [PATCH 21/95] fix: secure deploy --- templates/compose/documenso.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index f78c04f7f..26baad6c2 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -149,8 +149,4 @@ services: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-documenso} -d ${POSTGRES_DB:-documenso-db}"] interval: 5s timeout: 5s - retries: 10 - start_period: 10s - -volumes: - documenso_postgresql_data: \ No newline at end of file + retries: 10 \ No newline at end of file From c93c238be2758e9ddaf7a8b5685f5488e0fc5e99 Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 17:06:39 +0530 Subject: [PATCH 22/95] fix: secure deploy --- templates/compose/documenso.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 26baad6c2..87ed25c43 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -45,11 +45,10 @@ services: healthcheck: test: - CMD-SHELL - - "wget -q -O - http://localhost:3000/ | grep -q 'Sign in to your account' || exit 1" - interval: 10s - timeout: 5s - retries: 10 - start_period: 40s + - "wget -q -O - http://documenso:3000/ | grep -q 'Sign in to your account'" + interval: 2s + timeout: 10s + retries: 20 entrypoint: - /bin/sh - -c @@ -146,7 +145,6 @@ services: volumes: - documenso_postgresql_data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-documenso} -d ${POSTGRES_DB:-documenso-db}"] - interval: 5s - timeout: 5s + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + timeout: 20s retries: 10 \ No newline at end of file From e3c3962d07fdd86c2b8a8c893467c5f721ebf91b Mon Sep 17 00:00:00 2001 From: ajay Date: Fri, 7 Nov 2025 17:08:01 +0530 Subject: [PATCH 23/95] fix: updated postgres --- templates/compose/documenso.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/compose/documenso.yaml b/templates/compose/documenso.yaml index 87ed25c43..5c1398db5 100644 --- a/templates/compose/documenso.yaml +++ b/templates/compose/documenso.yaml @@ -139,12 +139,13 @@ services: database: image: postgres:17 environment: - - POSTGRES_USER=${POSTGRES_USER:-documenso} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-PLACEHOLDER_PASSWORD} + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - POSTGRES_DB=${POSTGRES_DB:-documenso-db} volumes: - documenso_postgresql_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s timeout: 20s retries: 10 \ No newline at end of file From 152801e2934130abb106d5dff89efdccc1107479 Mon Sep 17 00:00:00 2001 From: itssloplayz <155429915+itssloplayz@users.noreply.github.com> Date: Sat, 8 Nov 2025 11:59:26 +0100 Subject: [PATCH 24/95] Added tailscale template --- public/svgs/tailscale.svg | 7 ++++++ templates/compose/tailscale.yaml | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 public/svgs/tailscale.svg create mode 100644 templates/compose/tailscale.yaml diff --git a/public/svgs/tailscale.svg b/public/svgs/tailscale.svg new file mode 100644 index 000000000..cde7dbd50 --- /dev/null +++ b/public/svgs/tailscale.svg @@ -0,0 +1,7 @@ + + + Tailscale Streamline Icon: https://streamlinehq.com + + Tailscale + + \ No newline at end of file diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml new file mode 100644 index 000000000..3fd0ae622 --- /dev/null +++ b/templates/compose/tailscale.yaml @@ -0,0 +1,37 @@ +# documentation: https://tailscale.com/kb +# slogan: Tailscale securely connects your devices over the internet using WireGuard. +# category: networking +# tags: vpn, wireguard, remote-access +# logo: svgs/tailscale.svg + +version: '3.7' +services: + tailscale-nginx: + image: 'tailscale/tailscale:latest' + hostname: '${TS_HOSTNAME:-coolify-ts}' + environment: + - 'TS_HOSTNAME=${TS_HOSTNAME:-coolify-ts}' + - 'TS_AUTHKEY=${TS_AUTHKEY:-your_authkey}' + - 'TS_STATE_DIR=${TS_STATE_DIR:-/var/lib/tailscale}' + - 'TS_USERSPACE=${TS_USERSPACE:-false}' + volumes: + - 'tailscale-state:/var/lib/tailscale' + devices: + - '/dev/net/tun:/dev/net/tun' + cap_add: + - net_admin + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "tailscale status --json | grep -q 'BackendState'"] + interval: 10s + timeout: 5s + retries: 5 + + nginx: + image: nginx + depends_on: + - tailscale-nginx + network_mode: 'service:tailscale-nginx' + +volumes: + tailscale-state: null From e53ea044766956fda4ace788fbcd5e6bcb60d338 Mon Sep 17 00:00:00 2001 From: itssloplayz <155429915+itssloplayz@users.noreply.github.com> Date: Sat, 8 Nov 2025 12:03:54 +0100 Subject: [PATCH 25/95] Removed the old file that was left in on accident --- lang/si.json | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 lang/si.json diff --git a/lang/si.json b/lang/si.json deleted file mode 100644 index b674c4140..000000000 --- a/lang/si.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "auth.login": "Prijava", - "auth.login.authentik": "Prijava z Authentik", - "auth.login.azure": "Prijava z Microsoft", - "auth.login.bitbucket": "Prijava z Bitbucket", - "auth.login.clerk": "Prijava z Clerk", - "auth.login.discord": "Prijava z Discord", - "auth.login.github": "Prijava z GitHub", - "auth.login.gitlab": "Prijava z GitLab", - "auth.login.google": "Prijava z Google", - "auth.login.infomaniak": "Prijava z Infomaniak", - "auth.login.zitadel": "Prijava z Zitadel", - "auth.already_registered": "Ste že registrirani?", - "auth.confirm_password": "Potrdite geslo", - "auth.forgot_password_link": "Ste pozabili geslo?", - "auth.forgot_password_heading": "Obnovitev gesla", - "auth.forgot_password_send_email": "Pošlji e-pošto za ponastavitev gesla", - "auth.register_now": "Registracija", - "auth.logout": "Odjava", - "auth.register": "Registracija", - "auth.registration_disabled": "Registracija je onemogočena. Obrnite se na administratorja.", - "auth.reset_password": "Ponastavi geslo", - "auth.failed": "Ti podatki se ne ujemajo z našimi zapisi.", - "auth.failed.callback": "Obdelava povratnega klica ponudnika prijave ni uspela.", - "auth.failed.password": "Vneseno geslo je nepravilno.", - "auth.failed.email": "Če račun s tem e-poštnim naslovom obstaja, boste kmalu prejeli povezavo za ponastavitev gesla.", - "auth.throttle": "Preveč poskusov prijave. Poskusite znova čez :seconds sekund.", - "input.name": "Ime", - "input.email": "E-pošta", - "input.password": "Geslo", - "input.password.again": "Geslo znova", - "input.code": "Enkratna koda", - "input.recovery_code": "Koda za obnovitev", - "button.save": "Shrani", - "repository.url": "Primeri
Za javne repozitorije uporabite https://....
Za zasebne repozitorije uporabite git@....

https://github.com/coollabsio/coolify-examples bo izbral vejo main
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify bo izbral vejo nodejs-fastify.
https://gitea.com/sedlav/expressjs.git bo izbral vejo main.
https://gitlab.com/andrasbacsai/nodejs-example.git bo izbral vejo main.", - "service.stop": "Ta storitev bo ustavljena.", - "resource.docker_cleanup": "Zaženi čiščenje Dockerja (odstrani neuporabljene slike in predpomnilnik gradnje).", - "resource.non_persistent": "Vsi nepersistenčni podatki bodo izbrisani.", - "resource.delete_volumes": "Trajno izbriši vse volumne, povezane s tem virom.", - "resource.delete_connected_networks": "Trajno izbriši vse neprafiniirane omrežja, povezana s tem virom.", - "resource.delete_configurations": "Trajno izbriši vse konfiguracijske datoteke s strežnika.", - "database.delete_backups_locally": "Vse varnostne kopije bodo trajno izbrisane iz lokalnega shranjevanja.", - "warning.sslipdomain": "Vaša konfiguracija je shranjena, vendar domena sslip s https NI priporočljiva, saj so strežniki Let's Encrypt s to javno domeno omejeni (preverjanje SSL certifikata bo spodletelo).

Namesto tega uporabite svojo domeno." -} From c960b7107639f90f8f355f0b815d4c3c0eb5f30d Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Sun, 9 Nov 2025 16:05:04 +0100 Subject: [PATCH 26/95] Update opnform.yaml to use version 1.10.2 for API and UI images, remove SELF_HOSTED environment variable, and adjust environment variable syntax for consistency --- templates/compose/opnform.yaml | 52 ++++++++++++++++------------------ 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 7e311e5a6..8256d7021 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -10,7 +10,6 @@ x-shared-env: &shared-api-env APP_KEY: ${SERVICE_BASE64_APIKEY} APP_DEBUG: ${APP_DEBUG:-false} APP_URL: ${SERVICE_FQDN_OPNFORM} - SELF_HOSTED: ${SELF_HOSTED:-true} LOG_CHANNEL: errorlog LOG_LEVEL: ${LOG_LEVEL:-debug} FILESYSTEM_DRIVER: ${FILESYSTEM_DRIVER:-local} @@ -50,7 +49,7 @@ x-shared-env: &shared-api-env services: opnform-api: - image: jhumanj/opnform-api:1.10.1 + image: jhumanj/opnform-api:1.10.2 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -77,7 +76,7 @@ services: start_period: 60s api-worker: - image: jhumanj/opnform-api:1.10.1 + image: jhumanj/opnform-api:1.10.2 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -98,7 +97,7 @@ services: start_period: 30s api-scheduler: - image: jhumanj/opnform-api:1.10.1 + image: jhumanj/opnform-api:1.10.2 volumes: - api-storage:/usr/share/nginx/html/storage environment: @@ -122,15 +121,14 @@ services: start_period: 70s # Allow time for first scheduled run and cache write opnform-ui: - image: jhumanj/opnform-client:1.10.1 + image: jhumanj/opnform-client:1.10.2 environment: - NUXT_PUBLIC_APP_URL: ${NUXT_PUBLIC_APP_URL:-/} - NUXT_PUBLIC_API_BASE: ${NUXT_PUBLIC_API_BASE:-/api} - NUXT_PRIVATE_API_BASE: ${NUXT_PRIVATE_API_BASE:-http://nginx/api} - NUXT_PUBLIC_ENV: ${NUXT_PUBLIC_ENV:-production} - NUXT_PUBLIC_H_CAPTCHA_SITE_KEY: ${H_CAPTCHA_SITE_KEY} - NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY: ${RE_CAPTCHA_SITE_KEY} - NUXT_PUBLIC_ROOT_REDIRECT_URL: ${NUXT_PUBLIC_ROOT_REDIRECT_URL} + - NUXT_PUBLIC_APP_URL=/ + - NUXT_PUBLIC_API_BASE=/api + - NUXT_PRIVATE_API_BASE=http://nginx/api + - NUXT_PUBLIC_ENV=production + - NUXT_PUBLIC_H_CAPTCHA_SITE_KEY=${H_CAPTCHA_SITE_KEY} + - NUXT_PUBLIC_RE_CAPTCHA_SITE_KEY=${RE_CAPTCHA_SITE_KEY} healthcheck: test: ["CMD-SHELL", "wget --spider -q http://opnform-ui:3000/login || exit 1"] @@ -138,15 +136,18 @@ services: timeout: 10s retries: 3 start_period: 45s + depends_on: + opnform-api: + condition: service_healthy postgresql: image: postgres:16 volumes: - opnform-postgresql-data:/var/lib/postgresql/data environment: - POSTGRES_USER: ${SERVICE_USER_POSTGRESQL} - POSTGRES_PASSWORD: ${SERVICE_PASSWORD_POSTGRESQL} - POSTGRES_DB: ${POSTGRESQL_DATABASE:-opnform} + - POSTGRES_USER=${SERVICE_USER_POSTGRESQL} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL} + - POSTGRES_DB=${POSTGRESQL_DATABASE:-opnform} healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s @@ -156,7 +157,7 @@ services: redis: image: redis:7 environment: - REDIS_PASSWORD: ${SERVICE_PASSWORD_64_REDIS} + - REDIS_PASSWORD=${SERVICE_PASSWORD_64_REDIS} volumes: - redis-data:/data command: ["redis-server", "--requirepass", "${SERVICE_PASSWORD_64_REDIS}"] @@ -170,15 +171,17 @@ services: # used for reverse proxying the API service and Web service. nginx: image: nginx:1.29.2 + environment: + - SERVICE_URL_OPNFORM volumes: - type: bind - source: ./nginx/nginx.conf.template - target: /etc/nginx/conf.d/opnform.conf + source: ./nginx/nginx.conf + target: /etc/nginx/conf.d/default.conf read_only: true content: | - map $request_uri $api_uri { + map $original_uri $api_uri { ~^/api(/.*$) $1; - default $request_uri; + default $original_uri; } server { @@ -210,17 +213,10 @@ services: fastcgi_pass opnform-api:9000; fastcgi_index index.php; include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root/index.php; + fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; fastcgi_param REQUEST_URI $api_uri; } - - # Deny access to . files - location ~ /\. { - deny all; - } } - environment: - - SERVICE_FQDN_OPNFORM depends_on: - opnform-api - opnform-ui From 6efa1444df521a86068a97a4f2f2f5346b80eabb Mon Sep 17 00:00:00 2001 From: Rohit Tiwari Date: Mon, 10 Nov 2025 15:25:33 +0530 Subject: [PATCH 27/95] fixes default template of Redis Insight this PR refers the issue generated that , caused by untested template issue no : #7166 Redis Insight is inaccessible when installed with default configuration until manually change the test url --- templates/compose/redis-insight.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/redis-insight.yaml b/templates/compose/redis-insight.yaml index 2ba01c0c3..0e6056c6a 100644 --- a/templates/compose/redis-insight.yaml +++ b/templates/compose/redis-insight.yaml @@ -23,7 +23,7 @@ services: - CMD - wget - '--spider' - - 'http://localhost:5540' + - 'http://0.0.0.0:5540/api/health' interval: 10s retries: 3 timeout: 10s From 16a1b3610c61f88fe3959ed584a6964a88f3aafb Mon Sep 17 00:00:00 2001 From: Robin266 Date: Tue, 11 Nov 2025 22:58:22 +0100 Subject: [PATCH 28/95] add palworld service --- public/svgs/palworld.svg | 18 +++++ templates/compose/palworld.yaml | 131 ++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 public/svgs/palworld.svg create mode 100644 templates/compose/palworld.yaml diff --git a/public/svgs/palworld.svg b/public/svgs/palworld.svg new file mode 100644 index 000000000..f5fff5bc8 --- /dev/null +++ b/public/svgs/palworld.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/templates/compose/palworld.yaml b/templates/compose/palworld.yaml new file mode 100644 index 000000000..0f22a6314 --- /dev/null +++ b/templates/compose/palworld.yaml @@ -0,0 +1,131 @@ +version: '3.7' +services: + palworld: + image: 'thijsvanloef/palworld-server-docker:latest' + restart: unless-stopped + container_name: palworld-server + stop_grace_period: 30s + ports: + - '8211:8211/udp' + - '27015:27015/udp' + volumes: + - '${COOLIFY_VOLUME_APP}:/palworld/' + environment: + - 'TZ=${TZ:?UTC}' + - 'PUID=${PUID:?1000}' + - 'PGID=${PGID:?1000}' + - 'MULTITHREADING=${MULTITHREADING:?false}' + - 'MAX_PLAYERS=${PLAYERS:?16}' + - 'SERVER_NAME=${SERVER_NAME:?palworld-server-docker by Thijs van Loef via Coolify}' + - 'SERVER_DESCRIPTION=${SERVER_DESCRIPTION:?palworld-server-docker by Thijs van Loef via Coolify}' + - 'SERVER_PASSWORD=${SERVER_PASSWORD:?worldofpals}' + - 'ADMIN_PASSWORD=${ADMIN_PASSWORD:-adminPassword}' + - 'COMMUNITY=${COMMUNITY:?false}' + - 'PUBLIC_IP=${PUBLIC_IP:-}' + - 'PUBLIC_PORT=${PUBLIC_PORT:?8211}' + - 'PORT=${PORT:?8211}' + - 'QUERY_PORT=${QUERY_PORT:?27015}' + - 'UPDATE_ON_BOOT=${UPDATE_ON_BOOT:?true}' + - 'RCON_ENABLED=${RCON_ENABLED:?true}' + - 'RCON_PORT=${RCON_PORT:?25575}' + - 'BACKUP_ENABLED=${BACKUP_ENABLED:?true}' + - 'DELETE_OLD_BACKUPS=${DELETE_OLD_BACKUPS:?false}' + - 'OLD_BACKUP_DAYS=${OLD_BACKUP_DAYS:?30}' + - 'BACKUP_CRON_EXPRESSION=${BACKUP_CRON_EXPRESSION:?0 0 * * *}' + - 'AUTO_UPDATE_ENABLED=${AUTO_UPDATE_ENABLED:?false}' + - 'AUTO_UPDATE_CRON_EXPRESSION=${AUTO_UPDATE_CRON_EXPRESSION:?0 * * * *}' + - 'AUTO_UPDATE_WARN_MINUTES=${AUTO_UPDATE_WARN_MINUTES:?30}' + - 'AUTO_REBOOT_ENABLED=${AUTO_REBOOT_ENABLED:?false}' + - 'AUTO_REBOOT_EVEN_IF_PLAYERS_ONLINE=${AUTO_REBOOT_EVEN_IF_PLAYERS_ONLINE:?false}' + - 'AUTO_REBOOT_WARN_MINUTES=${AUTO_REBOOT_WARN_MINUTES:?5}' + - 'AUTO_REBOOT_CRON_EXPRESSION=${AUTO_REBOOT_CRON_EXPRESSION:?0 0 * * *}' + - 'AUTO_PAUSE_ENABLED=${AUTO_PAUSE_ENABLED:?false}' + - 'AUTO_PAUSE_TIMEOUT_EST=${AUTO_PAUSE_TIMEOUT_EST:?180}' + - 'AUTO_PAUSE_LOG=${AUTO_PAUSE_LOG:?true}' + - 'AUTO_PAUSE_DEBUG=${AUTO_PAUSE_DEBUG:?false}' + - 'ENABLE_PLAYER_LOGGING=${ENABLE_PLAYER_LOGGING:?true}' + - 'PLAYER_LOGGING_POLL_PERIOD=${PLAYER_LOGGING_POLL_PERIOD:?5}' + - 'DIFFICULTY=${DIFFICULTY:?None}' + - 'RANDOMIZER_TYPE=${RANDOMIZER_TYPE:-}' + - 'RANDOMIZER_SEED=${RANDOMIZER_SEED:?none}' + - 'DAYTIME_SPEEDRATE=${DAYTIME_SPEEDRATE:?1.000000}' + - 'NIGHTTIME_SPEEDRATE=${NIGHTTIME_SPEEDRATE:?1.000000}' + - 'EXP_RATE=${EXP_RATE:?1.000000}' + - 'PAL_CAPTURE_RATE=${PAL_CAPTURE_RATE:?1.000000}' + - 'PAL_SPAWN_NUM_RATE=${PAL_SPAWN_NUM_RATE:?1.000000}' + - 'PAL_DAMAGE_RATE_ATTACK=${PAL_DAMAGE_RATE_ATTACK:?1.000000}' + - 'PAL_DAMAGE_RATE_DEFENSE=${PAL_DAMAGE_RATE_DEFENSE:?1.000000}' + - 'PLAYER_DAMAGE_RATE_ATTACK=${PLAYER_DAMAGE_RATE_ATTACK:?1.000000}' + - 'PLAYER_DAMAGE_RATE_DEFENSE=${PLAYER_DAMAGE_RATE_DEFENSE:?1.000000}' + - 'PLAYER_STOMACH_DECREASE_RATE=${PLAYER_STOMACH_DECREASE_RATE:?1.000000}' + - 'PLAYER_STAMINA_DECREASE_RATE=${PLAYER_STAMINA_DECREASE_RATE:?1.000000}' + - 'PLAYER_AUTO_HP_REGEN_RATE=${PLAYER_AUTO_HP_REGEN_RATE:?1.000000}' + - 'PLAYER_AUTO_HP_REGEN_RATE_IN_SLEEP=${PLAYER_AUTO_HP_REGEN_RATE_IN_SLEEP:?1.000000}' + - 'PAL_STOMACH_DECREASE_RATE=${PAL_STOMACH_DECREASE_RATE:?1.000000}' + - 'PAL_STAMINA_DECREASE_RATE=${PAL_STAMINA_DECREASE_RATE:?1.000000}' + - 'PAL_AUTO_HP_REGEN_RATE=${PAL_AUTO_HP_REGEN_RATE:?1.000000}' + - 'PAL_AUTO_HP_REGEN_RATE_IN_SLEEP=${PAL_AUTO_HP_REGEN_RATE_IN_SLEEP:?1.000000}' + - 'BUILD_OBJECT_HP_RATE=${BUILD_OBJECT_HP_RATE:?1.000000}' + - 'BUILD_OBJECT_DAMAGE_RATE=${BUILD_OBJECT_DAMAGE_RATE:?1.000000}' + - 'BUILD_OBJECT_DETERIORATION_DAMAGE_RATE=${BUILD_OBJECT_DETERIORATION_DAMAGE_RATE:?1.000000}' + - 'COLLECTION_DROP_RATE=${COLLECTION_DROP_RATE:?1.000000}' + - 'COLLECTION_OBJECT_HP_RATE=${COLLECTION_OBJECT_HP_RATE:?1.000000}' + - 'COLLECTION_OBJECT_RESPAWN_SPEED_RATE=${COLLECTION_OBJECT_RESPAWN_SPEED_RATE:?1.000000}' + - 'ENEMY_DROP_ITEM_RATE=${ENEMY_DROP_ITEM_RATE:?1.000000}' + - 'DEATH_PENALTY=${DEATH_PENALTY:?All}' + - 'ENABLE_PLAYER_TO_PLAYER_DAMAGE=${ENABLE_PLAYER_TO_PLAYER_DAMAGE:?False}' + - 'ENABLE_FRIENDLY_FIRE=${ENABLE_FRIENDLY_FIRE:?False}' + - 'ENABLE_INVADER_ENEMY=${ENABLE_INVADER_ENEMY:?True}' + - 'ACTIVE_UNKO=${ACTIVE_UNKO:?False}' + - 'ENABLE_AIM_ASSIST_PAD=${ENABLE_AIM_ASSIST_PAD:?True}' + - 'ENABLE_AIM_ASSIST_KEYBOARD=${ENABLE_AIM_ASSIST_KEYBOARD:?False}' + - 'DROP_ITEM_MAX_NUM=${DROP_ITEM_MAX_NUM:?3000}' + - 'DROP_ITEM_MAX_NUM_UNKO=${DROP_ITEM_MAX_NUM_UNKO:?100}' + - 'BASE_CAMP_MAX_NUM=${BASE_CAMP_MAX_NUM:?128}' + - 'BASE_CAMP_WORKER_MAX_NUM=${BASE_CAMP_WORKER_MAX_NUM:?15}' + - 'DROP_ITEM_ALIVE_MAX_HOURS=${DROP_ITEM_ALIVE_MAX_HOURS:?1.000000}' + - 'AUTO_RESET_GUILD_NO_ONLINE_PLAYERS=${AUTO_RESET_GUILD_NO_ONLINE_PLAYERS:?False}' + - 'AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS=${AUTO_RESET_GUILD_TIME_NO_ONLINE_PLAYERS:?72.000000}' + - 'GUILD_PLAYER_MAX_NUM=${GUILD_PLAYER_MAX_NUM:?20}' + - 'BASE_CAMP_MAX_NUM_IN_GUILD=${BASE_CAMP_MAX_NUM_IN_GUILD:?4}' + - 'PAL_EGG_DEFAULT_HATCHING_TIME=${PAL_EGG_DEFAULT_HATCHING_TIME:?72.000000}' + - 'WORK_SPEED_RATE=${WORK_SPEED_RATE:?1.000000}' + - 'AUTO_SAVE_SPAN=${AUTO_SAVE_SPAN:?30.000000}' + - 'IS_MULTIPLAY=${IS_MULTIPLAY:?False}' + - 'IS_PVP=${IS_PVP:?False}' + - 'HARDCORE=${HARDCORE:?False}' + - 'PAL_LOST=${PAL_LOST:?False}' + - 'CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP=${CAN_PICKUP_OTHER_GUILD_DEATH_PENALTY_DROP:?False}' + - 'ENABLE_NON_LOGIN_PENALTY=${ENABLE_NON_LOGIN_PENALTY:?True}' + - 'ENABLE_FAST_TRAVEL=${ENABLE_FAST_TRAVEL:?True}' + - 'IS_START_LOCATION_SELECT_BY_MAP=${IS_START_LOCATION_SELECT_BY_MAP:?True}' + - 'EXIST_PLAYER_AFTER_LOGOUT=${EXIST_PLAYER_AFTER_LOGOUT:?False}' + - 'ENABLE_DEFENSE_OTHER_GUILD_PLAYER=${ENABLE_DEFENSE_OTHER_GUILD_PLAYER:?False}' + - 'INVISIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX=${INVISIBLE_OTHER_GUILD_BASE_CAMP_AREA_FX:?False}' + - 'BUILD_AREA_LIMIT=${BUILD_AREA_LIMIT:?False}' + - 'ITEM_WEIGHT_RATE=${ITEM_WEIGHT_RATE:?1.000000}' + - 'COOP_PLAYER_MAX_NUM=${COOP_PLAYER_MAX_NUM:?4}' + - 'REGION=${REGION:-}' + - 'USEAUTH=${USEAUTH:?True}' + - 'BAN_LIST_URL=${BAN_LIST_URL:?https://api.palworldgame.com/api/banlist.txt}' + - 'REST_API_ENABLED=${REST_API_ENABLED:?False}' + - 'REST_API_PORT=${REST_API_PORT:?8212}' + - 'SHOW_PLAYER_LIST=${SHOW_PLAYER_LIST:?True}' + - 'ENABLE_PREDATOR_BOSS_PAL=${ENABLE_PREDATOR_BOSS_PAL:?True}' + - 'MAX_BUILDING_LIMIT_NUM=${MAX_BUILDING_LIMIT_NUM:?0}' + - 'SERVER_REPLICATE_PAWN_CULL_DISTANCE=${SERVER_REPLICATE_PAWN_CULL_DISTANCE:?15000.000000}' + - 'SERVER_REPLICATE_PAWN_CULL_DISTANCE_IN_BASE_CAMP=${SERVER_REPLICATE_PAWN_CULL_DISTANCE_IN_BASE_CAMP:?5000.000000}' + - 'CROSSPLAY_PLATFORMS=${CROSSPLAY_PLATFORMS:?(Steam,Xbox,PS5,Mac)}' + - 'USE_BACKUP_SAVE_DATA=${USE_BACKUP_SAVE_DATA:?True}' + - 'USE_DEPOT_DOWNLOADER=${USE_DEPOT_DOWNLOADER:?False}' + - 'INSTALL_BETA_INSIDER=${INSTALL_BETA_INSIDER:?False}' + - 'ALLOW_GLOBAL_PALBOX_EXPORT=${ALLOW_GLOBAL_PALBOX_EXPORT:?True}' + - 'ALLOW_GLOBAL_PALBOX_IMPORT=${ALLOW_GLOBAL_PALBOX_IMPORT:?False}' + - 'EQUIPMENT_DURABILITY_DAMAGE_RATE=${EQUIPMENT_DURABILITY_DAMAGE_RATE:?1.000000}' + - 'ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL=${ITEM_CONTAINER_FORCE_MARK_DIRTY_INTERVAL:?1.000000}' + - 'BOX64_DYNAREC_STRONGMEM=${BOX64_DYNAREC_STRONGMEM:-}' + - 'BOX64_DYNAREC_BIGBLOCK=${BOX64_DYNAREC_BIGBLOCK:-}' + - 'BOX64_DYNAREC_SAFEFLAGS=${BOX64_DYNAREC_SAFEFLAGS:-}' + - 'BOX64_DYNAREC_FASTROUND=${BOX64_DYNAREC_FASTROUND:-}' + - 'BOX64_DYNAREC_FASTNAN=${BOX64_DYNAREC_FASTNAN:-}' + - 'BOX64_DYNAREC_X87DOUBLE=${BOX64_DYNAREC_X87DOUBLE:-}' From 496bafca1f8fc5534b022a432715e291d2f5fd90 Mon Sep 17 00:00:00 2001 From: Gabriel Peralta Date: Wed, 12 Nov 2025 11:59:36 -0300 Subject: [PATCH 29/95] Update newt-pangolin.yaml --- templates/compose/newt-pangolin.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/newt-pangolin.yaml b/templates/compose/newt-pangolin.yaml index 18183303d..7e2db3253 100644 --- a/templates/compose/newt-pangolin.yaml +++ b/templates/compose/newt-pangolin.yaml @@ -8,8 +8,8 @@ services: image: fosrl/newt:latest environment: - PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT:-https://pangolin.domain.tld} - - NEWT_ID=${NEWT_ID:-2ix2t8xk22ubpfy} - - NEWT_SECRET=${NEWT_SECRET:-nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2} + - NEWT_ID=${NEWT_ID:?} + - NEWT_SECRET=${NEWT_SECRET:?} healthcheck: test: ["CMD", "newt", "--version"] interval: 5s From d60e6838ab58f1680bc16084c02a32bc14dcf715 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Fri, 14 Nov 2025 10:23:37 +0000 Subject: [PATCH 30/95] Update templates/compose/opnform.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/opnform.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 8256d7021..85126a82c 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -9,7 +9,7 @@ x-shared-env: &shared-api-env APP_ENV: production APP_KEY: ${SERVICE_BASE64_APIKEY} APP_DEBUG: ${APP_DEBUG:-false} - APP_URL: ${SERVICE_FQDN_OPNFORM} + APP_URL: ${SERVICE_FQDN_NGINX} LOG_CHANNEL: errorlog LOG_LEVEL: ${LOG_LEVEL:-debug} FILESYSTEM_DRIVER: ${FILESYSTEM_DRIVER:-local} From 671d88623e391adea2727f42ac9c19dfd7c6c468 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Fri, 14 Nov 2025 10:23:50 +0000 Subject: [PATCH 31/95] Update templates/compose/opnform.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/opnform.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 85126a82c..71ae7c166 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -172,7 +172,7 @@ services: nginx: image: nginx:1.29.2 environment: - - SERVICE_URL_OPNFORM + - SERVICE_URL_NGINX volumes: - type: bind source: ./nginx/nginx.conf From 1126385c1baf0c3b52975d0e991b699d6fca94f0 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Fri, 14 Nov 2025 11:24:39 +0000 Subject: [PATCH 32/95] fix(opnform): update APP_URL environment variable and remove unused nginx environment variable --- templates/compose/opnform.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 71ae7c166..80624d948 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -9,7 +9,7 @@ x-shared-env: &shared-api-env APP_ENV: production APP_KEY: ${SERVICE_BASE64_APIKEY} APP_DEBUG: ${APP_DEBUG:-false} - APP_URL: ${SERVICE_FQDN_NGINX} + APP_URL: ${SERVICE_URL_NGINX} LOG_CHANNEL: errorlog LOG_LEVEL: ${LOG_LEVEL:-debug} FILESYSTEM_DRIVER: ${FILESYSTEM_DRIVER:-local} @@ -171,8 +171,6 @@ services: # used for reverse proxying the API service and Web service. nginx: image: nginx:1.29.2 - environment: - - SERVICE_URL_NGINX volumes: - type: bind source: ./nginx/nginx.conf From adc82dc7a9b96bca8560b5b9f3b1c704ed53f973 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Fri, 14 Nov 2025 14:46:56 +0000 Subject: [PATCH 33/95] feat(opnform): add SERVICE_URL_NGINX environment variable to nginx service --- templates/compose/opnform.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/compose/opnform.yaml b/templates/compose/opnform.yaml index 80624d948..682eb38b8 100644 --- a/templates/compose/opnform.yaml +++ b/templates/compose/opnform.yaml @@ -171,6 +171,8 @@ services: # used for reverse proxying the API service and Web service. nginx: image: nginx:1.29.2 + environment: + - SERVICE_URL_NGINX volumes: - type: bind source: ./nginx/nginx.conf From 0bfee3ad33a74e73d057b4fe525042bae952155c Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:33:47 +0530 Subject: [PATCH 34/95] fix(service): Ghost using invalid base url --- templates/compose/ghost.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/ghost.yaml b/templates/compose/ghost.yaml index 695b965e7..85464d731 100644 --- a/templates/compose/ghost.yaml +++ b/templates/compose/ghost.yaml @@ -12,7 +12,7 @@ services: - ghost-content-data:/var/lib/ghost/content environment: - SERVICE_URL_GHOST_2368 - - url=$SERVICE_URL_GHOST_2368 + - url=$SERVICE_URL_GHOST - database__client=mysql - database__connection__host=mysql - database__connection__user=$SERVICE_USER_MYSQL From 8b916ca228c88337802ff388aefc1e5292a8ac90 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:23:12 +0100 Subject: [PATCH 35/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index 3fd0ae622..97afac488 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -4,7 +4,6 @@ # tags: vpn, wireguard, remote-access # logo: svgs/tailscale.svg -version: '3.7' services: tailscale-nginx: image: 'tailscale/tailscale:latest' From 4d77d06ac0c7db389c3f698d3343b50a4a25960e Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:23:20 +0100 Subject: [PATCH 36/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index 97afac488..d40d6b050 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -5,7 +5,7 @@ # logo: svgs/tailscale.svg services: - tailscale-nginx: + tailscale-client: image: 'tailscale/tailscale:latest' hostname: '${TS_HOSTNAME:-coolify-ts}' environment: From 84800ba7f2e53444baac870a0fe737cfa8a05ff2 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:23:26 +0100 Subject: [PATCH 37/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index d40d6b050..d413ab208 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -14,7 +14,7 @@ services: - 'TS_STATE_DIR=${TS_STATE_DIR:-/var/lib/tailscale}' - 'TS_USERSPACE=${TS_USERSPACE:-false}' volumes: - - 'tailscale-state:/var/lib/tailscale' + - 'tailscale-client:/var/lib/tailscale' devices: - '/dev/net/tun:/dev/net/tun' cap_add: From ba6d54065359430945dd5c7bc1901bb4cb96ff02 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:23:45 +0100 Subject: [PATCH 38/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index d413ab208..1706ae839 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -27,7 +27,7 @@ services: retries: 5 nginx: - image: nginx + image: nginx:latest depends_on: - tailscale-nginx network_mode: 'service:tailscale-nginx' From d9eb0ab00b06f28147d444e26027c01778c2b920 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:23:51 +0100 Subject: [PATCH 39/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index 1706ae839..f9c90341c 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -19,7 +19,6 @@ services: - '/dev/net/tun:/dev/net/tun' cap_add: - net_admin - restart: unless-stopped healthcheck: test: ["CMD-SHELL", "tailscale status --json | grep -q 'BackendState'"] interval: 10s From 28b44bad8e238a04a7cf7b6cab2378db44814252 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:24:01 +0100 Subject: [PATCH 40/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index f9c90341c..f8cde9afc 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -10,7 +10,7 @@ services: hostname: '${TS_HOSTNAME:-coolify-ts}' environment: - 'TS_HOSTNAME=${TS_HOSTNAME:-coolify-ts}' - - 'TS_AUTHKEY=${TS_AUTHKEY:-your_authkey}' + - 'TS_AUTHKEY=${TS_AUTHKEY:?}' - 'TS_STATE_DIR=${TS_STATE_DIR:-/var/lib/tailscale}' - 'TS_USERSPACE=${TS_USERSPACE:-false}' volumes: From 6e24ef247a4e404b641d6bd0c0007bc3296e6d25 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:24:08 +0100 Subject: [PATCH 41/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index f8cde9afc..52df3f23c 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -28,7 +28,7 @@ services: nginx: image: nginx:latest depends_on: - - tailscale-nginx + - tailscale-client network_mode: 'service:tailscale-nginx' volumes: From ce5f40afd824828fb518b63f34bf2deb3ca880c6 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 21:13:12 +0100 Subject: [PATCH 42/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index 52df3f23c..c7166d695 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -30,6 +30,3 @@ services: depends_on: - tailscale-client network_mode: 'service:tailscale-nginx' - -volumes: - tailscale-state: null From 223770726303e720920c07f655703a52cfafeb8b Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 21:13:18 +0100 Subject: [PATCH 43/95] Update templates/compose/tailscale.yaml Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/tailscale.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale.yaml index c7166d695..ed675e795 100644 --- a/templates/compose/tailscale.yaml +++ b/templates/compose/tailscale.yaml @@ -29,4 +29,15 @@ services: image: nginx:latest depends_on: - tailscale-client - network_mode: 'service:tailscale-nginx' + network_mode: 'service:tailscale-client' + healthcheck: + test: + - CMD + - curl + - '-f' + - 'http://localhost:80/' + - '-o' + - /dev/null + interval: 20s + timeout: 5s + retries: 3 From 9a5967b77db130b7ef496b9907a6751f343a04e6 Mon Sep 17 00:00:00 2001 From: majcek210 <155429915+majcek210@users.noreply.github.com> Date: Fri, 14 Nov 2025 21:14:13 +0100 Subject: [PATCH 44/95] Rename tailscale.yaml > tailscale-client.yaml --- templates/compose/{tailscale.yaml => tailscale-client.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename templates/compose/{tailscale.yaml => tailscale-client.yaml} (100%) diff --git a/templates/compose/tailscale.yaml b/templates/compose/tailscale-client.yaml similarity index 100% rename from templates/compose/tailscale.yaml rename to templates/compose/tailscale-client.yaml From b345fc4468accfb054b5bdcfcfc5e14999882d1b Mon Sep 17 00:00:00 2001 From: hugoduar Date: Sat, 15 Nov 2025 02:19:22 -0600 Subject: [PATCH 45/95] chore(n8n): upgrade n8n image version to 1.119.2 in compose templates --- templates/compose/n8n-with-postgres-and-worker.yaml | 4 ++-- templates/compose/n8n-with-postgresql.yaml | 2 +- templates/compose/n8n.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/compose/n8n-with-postgres-and-worker.yaml b/templates/compose/n8n-with-postgres-and-worker.yaml index 5f6aa5e50..fec28860e 100644 --- a/templates/compose/n8n-with-postgres-and-worker.yaml +++ b/templates/compose/n8n-with-postgres-and-worker.yaml @@ -7,7 +7,7 @@ services: n8n: - image: docker.n8n.io/n8nio/n8n:1.114.4 + image: docker.n8n.io/n8nio/n8n:1.119.2 environment: - SERVICE_URL_N8N_5678 - N8N_EDITOR_BASE_URL=${SERVICE_URL_N8N} @@ -46,7 +46,7 @@ services: retries: 10 n8n-worker: - image: docker.n8n.io/n8nio/n8n:1.114.4 + image: docker.n8n.io/n8nio/n8n:1.119.2 command: worker environment: - GENERIC_TIMEZONE=${GENERIC_TIMEZONE:-Europe/Berlin} diff --git a/templates/compose/n8n-with-postgresql.yaml b/templates/compose/n8n-with-postgresql.yaml index 1a1592f79..94648e958 100644 --- a/templates/compose/n8n-with-postgresql.yaml +++ b/templates/compose/n8n-with-postgresql.yaml @@ -7,7 +7,7 @@ services: n8n: - image: docker.n8n.io/n8nio/n8n:1.114.4 + image: docker.n8n.io/n8nio/n8n:1.119.2 environment: - SERVICE_URL_N8N_5678 - N8N_EDITOR_BASE_URL=${SERVICE_URL_N8N} diff --git a/templates/compose/n8n.yaml b/templates/compose/n8n.yaml index 3078516a5..4e886b408 100644 --- a/templates/compose/n8n.yaml +++ b/templates/compose/n8n.yaml @@ -7,7 +7,7 @@ services: n8n: - image: docker.n8n.io/n8nio/n8n:1.114.4 + image: docker.n8n.io/n8nio/n8n:1.119.2 environment: - SERVICE_URL_N8N_5678 - N8N_EDITOR_BASE_URL=${SERVICE_URL_N8N} From c4dc8e1811862210027a8749c2d21344780ce4d6 Mon Sep 17 00:00:00 2001 From: Robin266 Date: Sat, 15 Nov 2025 19:34:18 +0100 Subject: [PATCH 46/95] update palworld docker-compose --- templates/compose/palworld.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/templates/compose/palworld.yaml b/templates/compose/palworld.yaml index 0f22a6314..c4594138c 100644 --- a/templates/compose/palworld.yaml +++ b/templates/compose/palworld.yaml @@ -1,15 +1,12 @@ -version: '3.7' services: palworld: - image: 'thijsvanloef/palworld-server-docker:latest' - restart: unless-stopped - container_name: palworld-server + image: thijsvanloef/palworld-server-docker:1.4.6 stop_grace_period: 30s ports: - '8211:8211/udp' - '27015:27015/udp' volumes: - - '${COOLIFY_VOLUME_APP}:/palworld/' + - 'palworld-data:/palworld/' environment: - 'TZ=${TZ:?UTC}' - 'PUID=${PUID:?1000}' From 92286a85b85804362a0e415b676d80931b6cb22c Mon Sep 17 00:00:00 2001 From: Robin <160772203+Schlvrws@users.noreply.github.com> Date: Sat, 15 Nov 2025 21:55:50 +0100 Subject: [PATCH 47/95] Update templates/compose/palworld.yaml remove unwanted character from compse file Co-authored-by: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> --- templates/compose/palworld.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/palworld.yaml b/templates/compose/palworld.yaml index c4594138c..4875d16f8 100644 --- a/templates/compose/palworld.yaml +++ b/templates/compose/palworld.yaml @@ -1,6 +1,6 @@ services: palworld: - image: thijsvanloef/palworld-server-docker:1.4.6 + image: thijsvanloef/palworld-server-docker:v1.4.6 stop_grace_period: 30s ports: - '8211:8211/udp' From 8a0749fddfc7fada6d15baac5c3c609e3b8c0788 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:33:10 +0530 Subject: [PATCH 48/95] Set network_mode to host for netbird client one click service --- templates/compose/netbird-client.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/compose/netbird-client.yaml b/templates/compose/netbird-client.yaml index a7c3f1fb6..4bc5e32e0 100644 --- a/templates/compose/netbird-client.yaml +++ b/templates/compose/netbird-client.yaml @@ -7,6 +7,7 @@ services: netbird-client: image: 'netbirdio/netbird:latest' + network_mode: host environment: - 'NB_SETUP_KEY=${NB_SETUP_KEY}' - 'NB_ENABLE_ROSENPASS=${NB_ENABLE_ROSENPASS:-false}' From f5beb3a84801a759ad69d80f0b8ae84a3bcff3a4 Mon Sep 17 00:00:00 2001 From: ShadowArcanist <162910371+ShadowArcanist@users.noreply.github.com> Date: Mon, 17 Nov 2025 00:20:47 +0530 Subject: [PATCH 49/95] fix(service): plausible compose parsing error --- templates/compose/plausible.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/compose/plausible.yaml b/templates/compose/plausible.yaml index 516c4f2cf..de1749e58 100644 --- a/templates/compose/plausible.yaml +++ b/templates/compose/plausible.yaml @@ -70,7 +70,7 @@ services: source: ./clickhouse/clickhouse-config.xml target: /etc/clickhouse-server/config.d/logging.xml read_only: true - content: "warningtrue" + content: 'warningtrue' - type: bind source: ./clickhouse/clickhouse-user-config.xml target: /etc/clickhouse-server/users.d/logging.xml From 3f7c5fbdf9306221f5e0cf1b3a334856cdeba0c6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:58:59 +0100 Subject: [PATCH 50/95] Consolidate AI documentation into .ai/ directory - Create .ai/ directory as single source of truth for all AI docs - Organize by topic: core/, development/, patterns/, meta/ - Update CLAUDE.md to reference .ai/ files instead of embedding content - Remove 18KB of duplicated Laravel Boost guidelines from CLAUDE.md - Fix testing command descriptions (pest runs all tests, not just unit) - Standardize version numbers (Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4) - Replace all .cursor/rules/*.mdc with single coolify-ai-docs.mdc reference - Delete dev_workflow.mdc (non-Coolify Task Master content) - Merge cursor_rules.mdc + self_improve.mdc into maintaining-docs.md - Update .AI_INSTRUCTIONS_SYNC.md to redirect to new location Benefits: - Single source of truth - no more duplication - Consistent versions across all documentation - Better organization by topic - Platform-agnostic .ai/ directory works for all AI tools - Reduced CLAUDE.md from 719 to ~320 lines - Clear cross-references between files --- .AI_INSTRUCTIONS_SYNC.md | 165 +----- .ai/README.md | 141 +++++ .../core/application-architecture.md | 5 - .../core/deployment-architecture.md | 5 - .../core/project-overview.md | 5 - .../core/technology-stack.md | 83 ++- .../development/development-workflow.md | 5 - .../development/laravel-boost.md | 3 - .../development/testing-patterns.md | 5 - .ai/meta/maintaining-docs.md | 171 ++++++ .ai/meta/sync-guide.md | 156 ++++++ .../patterns/api-and-routing.md | 5 - .../patterns/database-patterns.md | 5 - .../patterns/form-components.md | 5 - .../patterns/frontend-patterns.md | 5 - .../patterns/security-patterns.md | 5 - .cursor/rules/README.mdc | 297 ----------- .cursor/rules/coolify-ai-docs.mdc | 156 ++++++ .cursor/rules/cursor_rules.mdc | 59 --- .cursor/rules/dev_workflow.mdc | 219 -------- .cursor/rules/self_improve.mdc | 59 --- CLAUDE.md | 491 ++---------------- 22 files changed, 739 insertions(+), 1311 deletions(-) create mode 100644 .ai/README.md rename .cursor/rules/application-architecture.mdc => .ai/core/application-architecture.md (98%) rename .cursor/rules/deployment-architecture.mdc => .ai/core/deployment-architecture.md (97%) rename .cursor/rules/project-overview.mdc => .ai/core/project-overview.md (96%) rename .cursor/rules/technology-stack.mdc => .ai/core/technology-stack.md (67%) rename .cursor/rules/development-workflow.mdc => .ai/development/development-workflow.md (98%) rename .cursor/rules/laravel-boost.mdc => .ai/development/laravel-boost.md (99%) rename .cursor/rules/testing-patterns.mdc => .ai/development/testing-patterns.md (99%) create mode 100644 .ai/meta/maintaining-docs.md create mode 100644 .ai/meta/sync-guide.md rename .cursor/rules/api-and-routing.mdc => .ai/patterns/api-and-routing.md (98%) rename .cursor/rules/database-patterns.mdc => .ai/patterns/database-patterns.md (97%) rename .cursor/rules/form-components.mdc => .ai/patterns/form-components.md (98%) rename .cursor/rules/frontend-patterns.mdc => .ai/patterns/frontend-patterns.md (98%) rename .cursor/rules/security-patterns.mdc => .ai/patterns/security-patterns.md (99%) delete mode 100644 .cursor/rules/README.mdc create mode 100644 .cursor/rules/coolify-ai-docs.mdc delete mode 100644 .cursor/rules/cursor_rules.mdc delete mode 100644 .cursor/rules/dev_workflow.mdc delete mode 100644 .cursor/rules/self_improve.mdc diff --git a/.AI_INSTRUCTIONS_SYNC.md b/.AI_INSTRUCTIONS_SYNC.md index bbe0a90e1..b268064af 100644 --- a/.AI_INSTRUCTIONS_SYNC.md +++ b/.AI_INSTRUCTIONS_SYNC.md @@ -1,156 +1,41 @@ # AI Instructions Synchronization Guide -This document explains how AI instructions are organized and synchronized across different AI tools used with Coolify. +**This file has moved!** -## Overview +All AI documentation and synchronization guidelines are now in the `.ai/` directory. -Coolify maintains AI instructions in two parallel systems: +## New Locations -1. **CLAUDE.md** - For Claude Code (claude.ai/code) -2. **.cursor/rules/** - For Cursor IDE and other AI assistants +- **Sync Guide**: [.ai/meta/sync-guide.md](.ai/meta/sync-guide.md) +- **Maintaining Docs**: [.ai/meta/maintaining-docs.md](.ai/meta/maintaining-docs.md) +- **Documentation Hub**: [.ai/README.md](.ai/README.md) -Both systems share core principles but are optimized for their respective workflows. +## Quick Overview -## Structure - -### CLAUDE.md -- **Purpose**: Condensed, workflow-focused guide for Claude Code -- **Format**: Single markdown file -- **Includes**: - - Quick-reference development commands - - High-level architecture overview - - Core patterns and guidelines - - Embedded Laravel Boost guidelines - - References to detailed .cursor/rules/ documentation - -### .cursor/rules/ -- **Purpose**: Detailed, topic-specific documentation -- **Format**: Multiple .mdc files organized by topic -- **Structure**: - - `README.mdc` - Main index and overview - - `cursor_rules.mdc` - Maintenance guidelines - - Topic-specific files (testing-patterns.mdc, security-patterns.mdc, etc.) -- **Used by**: Cursor IDE, Claude Code (for detailed reference), other AI assistants - -## Cross-References - -Both systems reference each other: - -- **CLAUDE.md** → references `.cursor/rules/` for detailed documentation -- **.cursor/rules/README.mdc** → references `CLAUDE.md` for Claude Code workflow -- **.cursor/rules/cursor_rules.mdc** → notes that changes should sync with CLAUDE.md - -## Maintaining Consistency - -When updating AI instructions, follow these guidelines: - -### 1. Core Principles (MUST be consistent) -- Laravel version (currently Laravel 12) -- PHP version (8.4) -- Testing execution rules (Docker for Feature tests, mocking for Unit tests) -- Security patterns and authorization requirements -- Code style requirements (Pint, PSR-12) - -### 2. Where to Make Changes - -**For workflow changes** (how to run commands, development setup): -- Primary: `CLAUDE.md` -- Secondary: `.cursor/rules/development-workflow.mdc` - -**For architectural patterns** (how code should be structured): -- Primary: `.cursor/rules/` topic files -- Secondary: Reference in `CLAUDE.md` "Additional Documentation" section - -**For testing patterns**: -- Both: Must be synchronized -- `CLAUDE.md` - Contains condensed testing execution rules -- `.cursor/rules/testing-patterns.mdc` - Contains detailed examples and patterns - -### 3. Update Checklist - -When making significant changes: - -- [ ] Identify if change affects core principles (version numbers, critical patterns) -- [ ] Update primary location (CLAUDE.md or .cursor/rules/) -- [ ] Check if update affects cross-referenced content -- [ ] Update secondary location if needed -- [ ] Verify cross-references are still accurate -- [ ] Run: `./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc` (if applicable) - -### 4. Common Inconsistencies to Watch - -- **Version numbers**: Laravel, PHP, package versions -- **Testing instructions**: Docker execution requirements -- **File paths**: Ensure relative paths work from root -- **Command syntax**: Docker commands, artisan commands -- **Architecture decisions**: Laravel 10 structure vs Laravel 12+ structure - -## File Organization +All AI instructions are now organized in `.ai/` directory: ``` -/ -├── CLAUDE.md # Claude Code instructions (condensed) -├── .AI_INSTRUCTIONS_SYNC.md # This file -└── .cursor/ - └── rules/ - ├── README.mdc # Index and overview - ├── cursor_rules.mdc # Maintenance guide - ├── testing-patterns.mdc # Testing details - ├── development-workflow.mdc # Dev setup details - ├── security-patterns.mdc # Security details - ├── application-architecture.mdc - ├── deployment-architecture.mdc - ├── database-patterns.mdc - ├── frontend-patterns.mdc - ├── api-and-routing.mdc - ├── form-components.mdc - ├── technology-stack.mdc - ├── project-overview.mdc - └── laravel-boost.mdc # Laravel-specific patterns +.ai/ +├── README.md # Navigation hub +├── core/ # Project information +├── development/ # Dev workflows +├── patterns/ # Code patterns +└── meta/ # Documentation guides ``` -## Recent Updates +### For AI Assistants -### 2025-10-07 -- ✅ Added cross-references between CLAUDE.md and .cursor/rules/ -- ✅ Synchronized Laravel version (12) across all files -- ✅ Added comprehensive testing execution rules (Docker for Feature tests) -- ✅ Added test design philosophy (prefer mocking over database) -- ✅ Fixed inconsistencies in testing documentation -- ✅ Created this synchronization guide +- **Claude Code**: Use `CLAUDE.md` (references `.ai/` files) +- **Cursor IDE**: Use `.cursor/rules/coolify-ai-docs.mdc` (references `.ai/` files) +- **All Tools**: Browse `.ai/` directory for detailed documentation -## Maintenance Commands +### Key Principles -```bash -# Check for version inconsistencies -grep -r "Laravel [0-9]" CLAUDE.md .cursor/rules/*.mdc +1. **Single Source of Truth**: Each piece of information exists in ONE file only +2. **Cross-Reference**: Other files reference the source, don't duplicate +3. **Organized by Topic**: Core, Development, Patterns, Meta +4. **Version Consistency**: All versions in `.ai/core/technology-stack.md` -# Check for PHP version consistency -grep -r "PHP [0-9]" CLAUDE.md .cursor/rules/*.mdc +## For More Information -# Format all documentation -./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc - -# Search for specific patterns across all docs -grep -r "pattern_to_check" CLAUDE.md .cursor/rules/ -``` - -## Contributing - -When contributing documentation: - -1. Check both CLAUDE.md and .cursor/rules/ for existing documentation -2. Add to appropriate location(s) based on guidelines above -3. Add cross-references if creating new patterns -4. Update this file if changing organizational structure -5. Verify consistency before submitting PR - -## Questions? - -If unsure about where to document something: - -- **Quick reference / workflow** → CLAUDE.md -- **Detailed patterns / examples** → .cursor/rules/[topic].mdc -- **Both?** → Start with .cursor/rules/, then reference in CLAUDE.md - -When in doubt, prefer detailed documentation in .cursor/rules/ and concise references in CLAUDE.md. +See [.ai/meta/sync-guide.md](.ai/meta/sync-guide.md) for complete synchronization guidelines and [.ai/meta/maintaining-docs.md](.ai/meta/maintaining-docs.md) for documentation maintenance instructions. diff --git a/.ai/README.md b/.ai/README.md new file mode 100644 index 000000000..357de249d --- /dev/null +++ b/.ai/README.md @@ -0,0 +1,141 @@ +# Coolify AI Documentation + +Welcome to the Coolify AI documentation hub. This directory contains all AI assistant instructions organized by topic for easy navigation and maintenance. + +## Quick Start + +- **For Claude Code**: Start with [CLAUDE.md](CLAUDE.md) +- **For Cursor IDE**: Check `.cursor/rules/coolify-ai-docs.mdc` which references this directory +- **For Other AI Tools**: Continue reading below + +## Documentation Structure + +### 📚 Core Documentation +Essential project information and architecture: + +- **[Technology Stack](core/technology-stack.md)** - All versions, packages, and dependencies (Laravel 12.4.1, PHP 8.4.7, etc.) +- **[Project Overview](core/project-overview.md)** - What Coolify is and how it works +- **[Application Architecture](core/application-architecture.md)** - System design and component relationships +- **[Deployment Architecture](core/deployment-architecture.md)** - How deployments work end-to-end + +### 💻 Development +Day-to-day development practices: + +- **[Workflow](development/development-workflow.md)** - Development setup, commands, and daily workflows +- **[Testing Patterns](development/testing-patterns.md)** - How to write and run tests (Unit vs Feature, Docker requirements) +- **[Laravel Boost](development/laravel-boost.md)** - Laravel-specific guidelines and best practices + +### 🎨 Patterns +Code patterns and best practices by domain: + +- **[Database Patterns](patterns/database-patterns.md)** - Eloquent, migrations, relationships +- **[Frontend Patterns](patterns/frontend-patterns.md)** - Livewire, Alpine.js, Tailwind CSS +- **[Security Patterns](patterns/security-patterns.md)** - Authentication, authorization, security best practices +- **[Form Components](patterns/form-components.md)** - Enhanced form components with authorization +- **[API & Routing](patterns/api-and-routing.md)** - API design, routing conventions, REST patterns + +### 📖 Meta +Documentation about documentation: + +- **[Maintaining Docs](meta/maintaining-docs.md)** - How to update and improve this documentation +- **[Sync Guide](meta/sync-guide.md)** - Keeping documentation synchronized across tools + +## Quick Decision Tree + +**What do you need help with?** + +### Running Commands +→ [development/development-workflow.md](development/development-workflow.md) +- Frontend: `npm run dev`, `npm run build` +- Backend: `php artisan serve`, `php artisan migrate` +- Tests: Docker for Feature tests, mocking for Unit tests +- Code quality: `./vendor/bin/pint`, `./vendor/bin/phpstan` + +### Writing Tests +→ [development/testing-patterns.md](development/testing-patterns.md) +- **Unit tests**: No database, use mocking, run outside Docker +- **Feature tests**: Can use database, must run inside Docker +- Command: `docker exec coolify php artisan test` + +### Building UI +→ [patterns/frontend-patterns.md](patterns/frontend-patterns.md) or [patterns/form-components.md](patterns/form-components.md) +- Livewire components with server-side state +- Alpine.js for client-side interactivity +- Tailwind CSS 4.1.4 for styling +- Form components with built-in authorization + +### Database Work +→ [patterns/database-patterns.md](patterns/database-patterns.md) +- Eloquent ORM patterns +- Migration best practices +- Relationship definitions +- Query optimization + +### Security & Auth +→ [patterns/security-patterns.md](patterns/security-patterns.md) +- Team-based access control +- Policy and gate patterns +- Form authorization (canGate, canResource) +- API security + +### Laravel-Specific Questions +→ [development/laravel-boost.md](development/laravel-boost.md) +- Laravel 12 patterns +- Livewire 3 best practices +- Pest testing patterns +- Laravel conventions + +### Version Numbers +→ [core/technology-stack.md](core/technology-stack.md) +- **Single source of truth** for all version numbers +- Don't duplicate versions elsewhere, reference this file + +## Navigation Tips + +1. **Start broad**: Begin with project-overview or CLAUDE.md +2. **Get specific**: Navigate to topic-specific files for details +3. **Cross-reference**: Files link to related topics +4. **Single source**: Version numbers and critical data exist in ONE place only + +## For AI Assistants + +### Important Patterns to Follow + +**Testing Commands:** +- Unit tests: `./vendor/bin/pest tests/Unit` (no database, outside Docker) +- Feature tests: `docker exec coolify php artisan test` (requires database, inside Docker) +- NEVER run Feature tests outside Docker - they will fail with database connection errors + +**Version Numbers:** +- Always use exact versions from [technology-stack.md](core/technology-stack.md) +- Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4 +- Don't use "v12" or "8.4" - be precise + +**Form Authorization:** +- ALWAYS include `canGate` and `:canResource` on form components +- See [form-components.md](patterns/form-components.md) for examples + +**Livewire Components:** +- MUST have exactly ONE root element +- See [frontend-patterns.md](patterns/frontend-patterns.md) for details + +**Code Style:** +- Run `./vendor/bin/pint` before finalizing changes +- Follow PSR-12 standards +- Use PHP 8.4 features (constructor promotion, typed properties, etc.) + +## Contributing + +When updating documentation: +1. Read [meta/maintaining-docs.md](meta/maintaining-docs.md) +2. Follow the single source of truth principle +3. Update cross-references when moving content +4. Test all links work +5. Run Pint on markdown files if applicable + +## Questions? + +- **Claude Code users**: Check [CLAUDE.md](CLAUDE.md) first +- **Cursor IDE users**: Check `.cursor/rules/coolify-ai-docs.mdc` +- **Documentation issues**: See [meta/maintaining-docs.md](meta/maintaining-docs.md) +- **Sync issues**: See [meta/sync-guide.md](meta/sync-guide.md) diff --git a/.cursor/rules/application-architecture.mdc b/.ai/core/application-architecture.md similarity index 98% rename from .cursor/rules/application-architecture.mdc rename to .ai/core/application-architecture.md index ef8d549ad..daaac0eaa 100644 --- a/.cursor/rules/application-architecture.mdc +++ b/.ai/core/application-architecture.md @@ -1,8 +1,3 @@ ---- -description: Laravel application structure, patterns, and architectural decisions -globs: app/**/*.php, config/*.php, bootstrap/**/*.php -alwaysApply: false ---- # Coolify Application Architecture ## Laravel Project Structure diff --git a/.cursor/rules/deployment-architecture.mdc b/.ai/core/deployment-architecture.md similarity index 97% rename from .cursor/rules/deployment-architecture.mdc rename to .ai/core/deployment-architecture.md index 35ae6699b..ec19cd0cd 100644 --- a/.cursor/rules/deployment-architecture.mdc +++ b/.ai/core/deployment-architecture.md @@ -1,8 +1,3 @@ ---- -description: Docker orchestration, deployment workflows, and containerization patterns -globs: app/Jobs/*.php, app/Actions/Application/*.php, app/Actions/Server/*.php, docker/*.*, *.yml, *.yaml -alwaysApply: false ---- # Coolify Deployment Architecture ## Deployment Philosophy diff --git a/.cursor/rules/project-overview.mdc b/.ai/core/project-overview.md similarity index 96% rename from .cursor/rules/project-overview.mdc rename to .ai/core/project-overview.md index b615a5d3e..59fda4868 100644 --- a/.cursor/rules/project-overview.mdc +++ b/.ai/core/project-overview.md @@ -1,8 +1,3 @@ ---- -description: High-level project mission, core concepts, and architectural overview -globs: README.md, CONTRIBUTING.md, CHANGELOG.md, *.md -alwaysApply: false ---- # Coolify Project Overview ## What is Coolify? diff --git a/.cursor/rules/technology-stack.mdc b/.ai/core/technology-stack.md similarity index 67% rename from .cursor/rules/technology-stack.mdc rename to .ai/core/technology-stack.md index 2119a2ff1..b12534db7 100644 --- a/.cursor/rules/technology-stack.mdc +++ b/.ai/core/technology-stack.md @@ -1,23 +1,19 @@ ---- -description: Complete technology stack, dependencies, and infrastructure components -globs: composer.json, package.json, docker-compose*.yml, config/*.php -alwaysApply: false ---- # Coolify Technology Stack +Complete technology stack, dependencies, and infrastructure components. + ## Backend Framework ### **Laravel 12.4.1** (PHP Framework) -- **Location**: [composer.json](mdc:composer.json) - **Purpose**: Core application framework -- **Key Features**: +- **Key Features**: - Eloquent ORM for database interactions - Artisan CLI for development tasks - Queue system for background jobs - Event-driven architecture -### **PHP 8.4** -- **Requirement**: `^8.4` in [composer.json](mdc:composer.json) +### **PHP 8.4.7** +- **Requirement**: `^8.4` in composer.json - **Features Used**: - Typed properties and return types - Attributes for validation and configuration @@ -28,11 +24,11 @@ ## Frontend Stack ### **Livewire 3.5.20** (Primary Frontend Framework) - **Purpose**: Server-side rendering with reactive components -- **Location**: [app/Livewire/](mdc:app/Livewire/) +- **Location**: `app/Livewire/` - **Key Components**: - - [Dashboard.php](mdc:app/Livewire/Dashboard.php) - Main interface - - [ActivityMonitor.php](mdc:app/Livewire/ActivityMonitor.php) - Real-time monitoring - - [MonacoEditor.php](mdc:app/Livewire/MonacoEditor.php) - Code editor + - Dashboard - Main interface + - ActivityMonitor - Real-time monitoring + - MonacoEditor - Code editor ### **Alpine.js** (Client-Side Interactivity) - **Purpose**: Lightweight JavaScript for DOM manipulation @@ -40,8 +36,7 @@ ### **Alpine.js** (Client-Side Interactivity) - **Usage**: Declarative directives in Blade templates ### **Tailwind CSS 4.1.4** (Styling Framework) -- **Location**: [package.json](mdc:package.json) -- **Configuration**: [postcss.config.cjs](mdc:postcss.config.cjs) +- **Configuration**: `postcss.config.cjs` - **Extensions**: - `@tailwindcss/forms` - Form styling - `@tailwindcss/typography` - Content typography @@ -57,24 +52,24 @@ ## Database & Caching ### **PostgreSQL 15** (Primary Database) - **Purpose**: Main application data storage - **Features**: JSONB support, advanced indexing -- **Models**: [app/Models/](mdc:app/Models/) +- **Models**: `app/Models/` ### **Redis 7** (Caching & Real-time) -- **Purpose**: +- **Purpose**: - Session storage - Queue backend - Real-time data caching - WebSocket session management ### **Supported Databases** (For User Applications) -- **PostgreSQL**: [StandalonePostgresql.php](mdc:app/Models/StandalonePostgresql.php) -- **MySQL**: [StandaloneMysql.php](mdc:app/Models/StandaloneMysql.php) -- **MariaDB**: [StandaloneMariadb.php](mdc:app/Models/StandaloneMariadb.php) -- **MongoDB**: [StandaloneMongodb.php](mdc:app/Models/StandaloneMongodb.php) -- **Redis**: [StandaloneRedis.php](mdc:app/Models/StandaloneRedis.php) -- **KeyDB**: [StandaloneKeydb.php](mdc:app/Models/StandaloneKeydb.php) -- **Dragonfly**: [StandaloneDragonfly.php](mdc:app/Models/StandaloneDragonfly.php) -- **ClickHouse**: [StandaloneClickhouse.php](mdc:app/Models/StandaloneClickhouse.php) +- **PostgreSQL**: StandalonePostgresql +- **MySQL**: StandaloneMysql +- **MariaDB**: StandaloneMariadb +- **MongoDB**: StandaloneMongodb +- **Redis**: StandaloneRedis +- **KeyDB**: StandaloneKeydb +- **Dragonfly**: StandaloneDragonfly +- **ClickHouse**: StandaloneClickhouse ## Authentication & Security @@ -101,7 +96,7 @@ ### **Laravel Horizon 5.30.3** ### **Queue System** - **Backend**: Redis-based queues -- **Jobs**: [app/Jobs/](mdc:app/Jobs/) +- **Jobs**: `app/Jobs/` - **Processing**: Background deployment and monitoring tasks ## Development Tools @@ -130,21 +125,21 @@ ### **Git Providers** - **Gitea**: Self-hosted Git service ### **Cloud Storage** -- **AWS S3**: [league/flysystem-aws-s3-v3](mdc:composer.json) -- **SFTP**: [league/flysystem-sftp-v3](mdc:composer.json) +- **AWS S3**: league/flysystem-aws-s3-v3 +- **SFTP**: league/flysystem-sftp-v3 - **Local Storage**: File system integration ### **Notification Services** -- **Email**: [resend/resend-laravel](mdc:composer.json) +- **Email**: resend/resend-laravel - **Discord**: Custom webhook integration - **Slack**: Webhook notifications - **Telegram**: Bot API integration - **Pushover**: Push notifications ### **Monitoring & Logging** -- **Sentry**: [sentry/sentry-laravel](mdc:composer.json) - Error tracking -- **Laravel Ray**: [spatie/laravel-ray](mdc:composer.json) - Debug tool -- **Activity Log**: [spatie/laravel-activitylog](mdc:composer.json) +- **Sentry**: sentry/sentry-laravel - Error tracking +- **Laravel Ray**: spatie/laravel-ray - Debug tool +- **Activity Log**: spatie/laravel-activitylog ## DevOps & Infrastructure @@ -181,9 +176,9 @@ ### **Monaco Editor** ## API & Documentation ### **OpenAPI/Swagger** -- **Documentation**: [openapi.json](mdc:openapi.json) (373KB) -- **Generator**: [zircote/swagger-php](mdc:composer.json) -- **API Routes**: [routes/api.php](mdc:routes/api.php) +- **Documentation**: openapi.json (373KB) +- **Generator**: zircote/swagger-php +- **API Routes**: `routes/api.php` ### **WebSocket Communication** - **Laravel Echo**: Real-time event broadcasting @@ -192,7 +187,7 @@ ### **WebSocket Communication** ## Package Management -### **PHP Dependencies** ([composer.json](mdc:composer.json)) +### **PHP Dependencies** (composer.json) ```json { "require": { @@ -205,7 +200,7 @@ ### **PHP Dependencies** ([composer.json](mdc:composer.json)) } ``` -### **JavaScript Dependencies** ([package.json](mdc:package.json)) +### **JavaScript Dependencies** (package.json) ```json { "devDependencies": { @@ -223,15 +218,15 @@ ### **JavaScript Dependencies** ([package.json](mdc:package.json)) ## Configuration Files ### **Build Configuration** -- **[vite.config.js](mdc:vite.config.js)**: Frontend build setup -- **[postcss.config.cjs](mdc:postcss.config.cjs)**: CSS processing -- **[rector.php](mdc:rector.php)**: PHP refactoring rules -- **[pint.json](mdc:pint.json)**: Code style configuration +- **vite.config.js**: Frontend build setup +- **postcss.config.cjs**: CSS processing +- **rector.php**: PHP refactoring rules +- **pint.json**: Code style configuration ### **Testing Configuration** -- **[phpunit.xml](mdc:phpunit.xml)**: Unit test configuration -- **[phpunit.dusk.xml](mdc:phpunit.dusk.xml)**: Browser test configuration -- **[tests/Pest.php](mdc:tests/Pest.php)**: Pest testing setup +- **phpunit.xml**: Unit test configuration +- **phpunit.dusk.xml**: Browser test configuration +- **tests/Pest.php**: Pest testing setup ## Version Requirements diff --git a/.cursor/rules/development-workflow.mdc b/.ai/development/development-workflow.md similarity index 98% rename from .cursor/rules/development-workflow.mdc rename to .ai/development/development-workflow.md index 175b7d85a..4ee376696 100644 --- a/.cursor/rules/development-workflow.mdc +++ b/.ai/development/development-workflow.md @@ -1,8 +1,3 @@ ---- -description: Development setup, coding standards, contribution guidelines, and best practices -globs: **/*.php, composer.json, package.json, *.md, .env.example -alwaysApply: false ---- # Coolify Development Workflow ## Development Environment Setup diff --git a/.cursor/rules/laravel-boost.mdc b/.ai/development/laravel-boost.md similarity index 99% rename from .cursor/rules/laravel-boost.mdc rename to .ai/development/laravel-boost.md index c409a4647..7f5922d94 100644 --- a/.cursor/rules/laravel-boost.mdc +++ b/.ai/development/laravel-boost.md @@ -1,6 +1,3 @@ ---- -alwaysApply: true ---- === foundation rules === diff --git a/.cursor/rules/testing-patterns.mdc b/.ai/development/testing-patterns.md similarity index 99% rename from .cursor/rules/testing-patterns.mdc rename to .ai/development/testing-patterns.md index 8d250b56a..875de8b3b 100644 --- a/.cursor/rules/testing-patterns.mdc +++ b/.ai/development/testing-patterns.md @@ -1,8 +1,3 @@ ---- -description: Testing strategies with Pest PHP, Laravel Dusk, and quality assurance patterns -globs: tests/**/*.php, database/factories/*.php -alwaysApply: false ---- # Coolify Testing Architecture & Patterns > **Cross-Reference**: These detailed testing patterns align with the testing guidelines in **[CLAUDE.md](mdc:CLAUDE.md)**. Both documents share the same core principles about Docker execution and mocking preferences. diff --git a/.ai/meta/maintaining-docs.md b/.ai/meta/maintaining-docs.md new file mode 100644 index 000000000..22e3c7c67 --- /dev/null +++ b/.ai/meta/maintaining-docs.md @@ -0,0 +1,171 @@ +# Maintaining AI Documentation + +Guidelines for creating and maintaining AI documentation to ensure consistency and effectiveness across all AI tools (Claude Code, Cursor IDE, etc.). + +## Documentation Structure + +All AI documentation lives in the `.ai/` directory with the following structure: + +``` +.ai/ +├── README.md # Navigation hub +├── CLAUDE.md # Main Claude Code instructions +├── core/ # Core project information +├── development/ # Development practices +├── patterns/ # Code patterns and best practices +└── meta/ # Documentation maintenance guides +``` + +## Required File Structure + +When creating new documentation files: + +```markdown +# Title + +Brief description of what this document covers. + +## Section 1 + +- **Main Points in Bold** + - Sub-points with details + - Examples and explanations + +## Section 2 + +### Subsection + +Content with code examples: + +```language +// ✅ DO: Show good examples +const goodExample = true; + +// ❌ DON'T: Show anti-patterns +const badExample = false; +``` +``` + +## File References + +- Use relative paths: `See [technology-stack.md](../core/technology-stack.md)` +- For code references: `` `app/Models/Application.php` `` +- Keep links working across different tools + +## Content Guidelines + +### DO: +- Start with high-level overview +- Include specific, actionable requirements +- Show examples of correct implementation +- Reference existing code when possible +- Keep documentation DRY by cross-referencing +- Use bullet points for clarity +- Include both DO and DON'T examples + +### DON'T: +- Create theoretical examples when real code exists +- Duplicate content across multiple files +- Use tool-specific formatting that won't work elsewhere +- Make assumptions about versions - specify exact versions + +## Rule Improvement Triggers + +Update documentation when you notice: +- New code patterns not covered by existing docs +- Repeated similar implementations across files +- Common error patterns that could be prevented +- New libraries or tools being used consistently +- Emerging best practices in the codebase + +## Analysis Process + +When updating documentation: +1. Compare new code with existing rules +2. Identify patterns that should be standardized +3. Look for references to external documentation +4. Check for consistent error handling patterns +5. Monitor test patterns and coverage + +## Rule Updates + +### Add New Documentation When: +- A new technology/pattern is used in 3+ files +- Common bugs could be prevented by documentation +- Code reviews repeatedly mention the same feedback +- New security or performance patterns emerge + +### Modify Existing Documentation When: +- Better examples exist in the codebase +- Additional edge cases are discovered +- Related documentation has been updated +- Implementation details have changed + +## Quality Checks + +Before committing documentation changes: +- [ ] Documentation is actionable and specific +- [ ] Examples come from actual code +- [ ] References are up to date +- [ ] Patterns are consistently enforced +- [ ] Cross-references work correctly +- [ ] Version numbers are exact and current + +## Continuous Improvement + +- Monitor code review comments +- Track common development questions +- Update docs after major refactors +- Add links to relevant documentation +- Cross-reference related docs + +## Deprecation + +When patterns become outdated: +1. Mark outdated patterns as deprecated +2. Remove docs that no longer apply +3. Update references to deprecated patterns +4. Document migration paths for old patterns + +## Synchronization + +### Single Source of Truth +- Each piece of information should exist in exactly ONE location +- Other files should reference the source, not duplicate it +- Example: Version numbers live in `core/technology-stack.md`, other files reference it + +### Cross-Tool Compatibility +- **CLAUDE.md**: Main instructions for Claude Code users (references `.ai/` files) +- **.cursor/rules/**: Single master file pointing to `.ai/` documentation +- **Both tools**: Should get same information from `.ai/` directory + +### When to Update What + +**Version Changes** (Laravel, PHP, packages): +1. Update `core/technology-stack.md` (single source) +2. Verify CLAUDE.md references it correctly +3. No other files should duplicate version numbers + +**Workflow Changes** (commands, setup): +1. Update `development/workflow.md` +2. Ensure CLAUDE.md quick reference is updated +3. Verify all cross-references work + +**Pattern Changes** (how to write code): +1. Update appropriate file in `patterns/` +2. Add/update examples from real codebase +3. Cross-reference from related docs + +## Documentation Files + +Keep documentation files only when explicitly needed. Don't create docs that merely describe obvious functionality - the code itself should be clear. + +## Breaking Changes + +When making breaking changes to documentation structure: +1. Update this maintaining-docs.md file +2. Update `.ai/README.md` navigation +3. Update CLAUDE.md references +4. Update `.cursor/rules/coolify-ai-docs.mdc` +5. Test all cross-references still work +6. Document the changes in sync-guide.md diff --git a/.ai/meta/sync-guide.md b/.ai/meta/sync-guide.md new file mode 100644 index 000000000..bbe0a90e1 --- /dev/null +++ b/.ai/meta/sync-guide.md @@ -0,0 +1,156 @@ +# AI Instructions Synchronization Guide + +This document explains how AI instructions are organized and synchronized across different AI tools used with Coolify. + +## Overview + +Coolify maintains AI instructions in two parallel systems: + +1. **CLAUDE.md** - For Claude Code (claude.ai/code) +2. **.cursor/rules/** - For Cursor IDE and other AI assistants + +Both systems share core principles but are optimized for their respective workflows. + +## Structure + +### CLAUDE.md +- **Purpose**: Condensed, workflow-focused guide for Claude Code +- **Format**: Single markdown file +- **Includes**: + - Quick-reference development commands + - High-level architecture overview + - Core patterns and guidelines + - Embedded Laravel Boost guidelines + - References to detailed .cursor/rules/ documentation + +### .cursor/rules/ +- **Purpose**: Detailed, topic-specific documentation +- **Format**: Multiple .mdc files organized by topic +- **Structure**: + - `README.mdc` - Main index and overview + - `cursor_rules.mdc` - Maintenance guidelines + - Topic-specific files (testing-patterns.mdc, security-patterns.mdc, etc.) +- **Used by**: Cursor IDE, Claude Code (for detailed reference), other AI assistants + +## Cross-References + +Both systems reference each other: + +- **CLAUDE.md** → references `.cursor/rules/` for detailed documentation +- **.cursor/rules/README.mdc** → references `CLAUDE.md` for Claude Code workflow +- **.cursor/rules/cursor_rules.mdc** → notes that changes should sync with CLAUDE.md + +## Maintaining Consistency + +When updating AI instructions, follow these guidelines: + +### 1. Core Principles (MUST be consistent) +- Laravel version (currently Laravel 12) +- PHP version (8.4) +- Testing execution rules (Docker for Feature tests, mocking for Unit tests) +- Security patterns and authorization requirements +- Code style requirements (Pint, PSR-12) + +### 2. Where to Make Changes + +**For workflow changes** (how to run commands, development setup): +- Primary: `CLAUDE.md` +- Secondary: `.cursor/rules/development-workflow.mdc` + +**For architectural patterns** (how code should be structured): +- Primary: `.cursor/rules/` topic files +- Secondary: Reference in `CLAUDE.md` "Additional Documentation" section + +**For testing patterns**: +- Both: Must be synchronized +- `CLAUDE.md` - Contains condensed testing execution rules +- `.cursor/rules/testing-patterns.mdc` - Contains detailed examples and patterns + +### 3. Update Checklist + +When making significant changes: + +- [ ] Identify if change affects core principles (version numbers, critical patterns) +- [ ] Update primary location (CLAUDE.md or .cursor/rules/) +- [ ] Check if update affects cross-referenced content +- [ ] Update secondary location if needed +- [ ] Verify cross-references are still accurate +- [ ] Run: `./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc` (if applicable) + +### 4. Common Inconsistencies to Watch + +- **Version numbers**: Laravel, PHP, package versions +- **Testing instructions**: Docker execution requirements +- **File paths**: Ensure relative paths work from root +- **Command syntax**: Docker commands, artisan commands +- **Architecture decisions**: Laravel 10 structure vs Laravel 12+ structure + +## File Organization + +``` +/ +├── CLAUDE.md # Claude Code instructions (condensed) +├── .AI_INSTRUCTIONS_SYNC.md # This file +└── .cursor/ + └── rules/ + ├── README.mdc # Index and overview + ├── cursor_rules.mdc # Maintenance guide + ├── testing-patterns.mdc # Testing details + ├── development-workflow.mdc # Dev setup details + ├── security-patterns.mdc # Security details + ├── application-architecture.mdc + ├── deployment-architecture.mdc + ├── database-patterns.mdc + ├── frontend-patterns.mdc + ├── api-and-routing.mdc + ├── form-components.mdc + ├── technology-stack.mdc + ├── project-overview.mdc + └── laravel-boost.mdc # Laravel-specific patterns +``` + +## Recent Updates + +### 2025-10-07 +- ✅ Added cross-references between CLAUDE.md and .cursor/rules/ +- ✅ Synchronized Laravel version (12) across all files +- ✅ Added comprehensive testing execution rules (Docker for Feature tests) +- ✅ Added test design philosophy (prefer mocking over database) +- ✅ Fixed inconsistencies in testing documentation +- ✅ Created this synchronization guide + +## Maintenance Commands + +```bash +# Check for version inconsistencies +grep -r "Laravel [0-9]" CLAUDE.md .cursor/rules/*.mdc + +# Check for PHP version consistency +grep -r "PHP [0-9]" CLAUDE.md .cursor/rules/*.mdc + +# Format all documentation +./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc + +# Search for specific patterns across all docs +grep -r "pattern_to_check" CLAUDE.md .cursor/rules/ +``` + +## Contributing + +When contributing documentation: + +1. Check both CLAUDE.md and .cursor/rules/ for existing documentation +2. Add to appropriate location(s) based on guidelines above +3. Add cross-references if creating new patterns +4. Update this file if changing organizational structure +5. Verify consistency before submitting PR + +## Questions? + +If unsure about where to document something: + +- **Quick reference / workflow** → CLAUDE.md +- **Detailed patterns / examples** → .cursor/rules/[topic].mdc +- **Both?** → Start with .cursor/rules/, then reference in CLAUDE.md + +When in doubt, prefer detailed documentation in .cursor/rules/ and concise references in CLAUDE.md. diff --git a/.cursor/rules/api-and-routing.mdc b/.ai/patterns/api-and-routing.md similarity index 98% rename from .cursor/rules/api-and-routing.mdc rename to .ai/patterns/api-and-routing.md index 8321205ac..ceaadaad5 100644 --- a/.cursor/rules/api-and-routing.mdc +++ b/.ai/patterns/api-and-routing.md @@ -1,8 +1,3 @@ ---- -description: RESTful API design, routing patterns, webhooks, and HTTP communication -globs: routes/*.php, app/Http/Controllers/**/*.php, app/Http/Resources/*.php, app/Http/Requests/*.php -alwaysApply: false ---- # Coolify API & Routing Architecture ## Routing Structure diff --git a/.cursor/rules/database-patterns.mdc b/.ai/patterns/database-patterns.md similarity index 97% rename from .cursor/rules/database-patterns.mdc rename to .ai/patterns/database-patterns.md index ec60a43b3..1e40ea152 100644 --- a/.cursor/rules/database-patterns.mdc +++ b/.ai/patterns/database-patterns.md @@ -1,8 +1,3 @@ ---- -description: Database architecture, models, migrations, relationships, and data management patterns -globs: app/Models/*.php, database/migrations/*.php, database/seeders/*.php, app/Actions/Database/*.php -alwaysApply: false ---- # Coolify Database Architecture & Patterns ## Database Strategy diff --git a/.cursor/rules/form-components.mdc b/.ai/patterns/form-components.md similarity index 98% rename from .cursor/rules/form-components.mdc rename to .ai/patterns/form-components.md index 665ccfd98..3ff1d0f81 100644 --- a/.cursor/rules/form-components.mdc +++ b/.ai/patterns/form-components.md @@ -1,8 +1,3 @@ ---- -description: Enhanced form components with built-in authorization system -globs: resources/views/**/*.blade.php, app/View/Components/Forms/*.php -alwaysApply: true ---- # Enhanced Form Components with Authorization diff --git a/.cursor/rules/frontend-patterns.mdc b/.ai/patterns/frontend-patterns.md similarity index 98% rename from .cursor/rules/frontend-patterns.mdc rename to .ai/patterns/frontend-patterns.md index 4730160b2..ecd21a8d8 100644 --- a/.cursor/rules/frontend-patterns.mdc +++ b/.ai/patterns/frontend-patterns.md @@ -1,8 +1,3 @@ ---- -description: Livewire components, Alpine.js patterns, Tailwind CSS, and enhanced form components -globs: app/Livewire/**/*.php, resources/views/**/*.blade.php, resources/js/**/*.js, resources/css/**/*.css -alwaysApply: false ---- # Coolify Frontend Architecture & Patterns ## Frontend Philosophy diff --git a/.cursor/rules/security-patterns.mdc b/.ai/patterns/security-patterns.md similarity index 99% rename from .cursor/rules/security-patterns.mdc rename to .ai/patterns/security-patterns.md index a7ab2ad69..ac1470ac9 100644 --- a/.cursor/rules/security-patterns.mdc +++ b/.ai/patterns/security-patterns.md @@ -1,8 +1,3 @@ ---- -description: Security architecture, authentication, authorization patterns, and enhanced form component security -globs: app/Policies/*.php, app/View/Components/Forms/*.php, app/Http/Middleware/*.php, resources/views/**/*.blade.php -alwaysApply: true ---- # Coolify Security Architecture & Patterns ## Security Philosophy diff --git a/.cursor/rules/README.mdc b/.cursor/rules/README.mdc deleted file mode 100644 index d0597bb72..000000000 --- a/.cursor/rules/README.mdc +++ /dev/null @@ -1,297 +0,0 @@ ---- -description: Complete guide to Coolify Cursor rules and development patterns -globs: .cursor/rules/*.mdc -alwaysApply: false ---- -# Coolify Cursor Rules - Complete Guide - -## Overview - -This comprehensive set of Cursor Rules provides deep insights into **Coolify**, an open-source self-hostable alternative to Heroku/Netlify/Vercel. These rules will help you understand, navigate, and contribute to this complex Laravel-based deployment platform. - -> **Cross-Reference**: This directory is for **detailed, topic-specific rules** used by Cursor IDE and other AI assistants. For Claude Code specifically, also see **[CLAUDE.md](mdc:CLAUDE.md)** which provides a condensed, workflow-focused guide. Both systems share core principles but are optimized for their respective tools. -> -> **Maintaining Rules**: When updating these rules, see **[.AI_INSTRUCTIONS_SYNC.md](mdc:.AI_INSTRUCTIONS_SYNC.md)** for synchronization guidelines to keep CLAUDE.md and .cursor/rules/ consistent. - -## Rule Categories - -### 🏗️ Architecture & Foundation -- **[project-overview.mdc](mdc:.cursor/rules/project-overview.mdc)** - What Coolify is and its core mission -- **[technology-stack.mdc](mdc:.cursor/rules/technology-stack.mdc)** - Complete technology stack and dependencies -- **[application-architecture.mdc](mdc:.cursor/rules/application-architecture.mdc)** - Laravel application structure and patterns - -### 🎨 Frontend Development -- **[frontend-patterns.mdc](mdc:.cursor/rules/frontend-patterns.mdc)** - Livewire + Alpine.js + Tailwind architecture -- **[form-components.mdc](mdc:.cursor/rules/form-components.mdc)** - Enhanced form components with built-in authorization - -### 🗄️ Data & Backend -- **[database-patterns.mdc](mdc:.cursor/rules/database-patterns.mdc)** - Database architecture, models, and data management -- **[deployment-architecture.mdc](mdc:.cursor/rules/deployment-architecture.mdc)** - Docker orchestration and deployment workflows - -### 🌐 API & Communication -- **[api-and-routing.mdc](mdc:.cursor/rules/api-and-routing.mdc)** - RESTful APIs, webhooks, and routing patterns - -### 🧪 Quality Assurance -- **[testing-patterns.mdc](mdc:.cursor/rules/testing-patterns.mdc)** - Testing strategies with Pest PHP and Laravel Dusk - -### 🔧 Development Process -- **[development-workflow.mdc](mdc:.cursor/rules/development-workflow.mdc)** - Development setup, coding standards, and contribution guidelines - -### 🔒 Security -- **[security-patterns.mdc](mdc:.cursor/rules/security-patterns.mdc)** - Security architecture, authentication, and best practices - -## Quick Navigation - -### Core Application Files -- **[app/Models/Application.php](mdc:app/Models/Application.php)** - Main application entity (74KB, highly complex) -- **[app/Models/Server.php](mdc:app/Models/Server.php)** - Server management (46KB, complex) -- **[app/Models/Service.php](mdc:app/Models/Service.php)** - Service definitions (58KB, complex) -- **[app/Models/Team.php](mdc:app/Models/Team.php)** - Multi-tenant structure (8.9KB) - -### Configuration Files -- **[composer.json](mdc:composer.json)** - PHP dependencies and Laravel setup -- **[package.json](mdc:package.json)** - Frontend dependencies and build scripts -- **[vite.config.js](mdc:vite.config.js)** - Frontend build configuration -- **[docker-compose.dev.yml](mdc:docker-compose.dev.yml)** - Development environment - -### API Documentation -- **[openapi.json](mdc:openapi.json)** - Complete API documentation (373KB) -- **[routes/api.php](mdc:routes/api.php)** - API endpoint definitions (13KB) -- **[routes/web.php](mdc:routes/web.php)** - Web application routes (21KB) - -## Key Concepts to Understand - -### 1. Multi-Tenant Architecture -Coolify uses a **team-based multi-tenancy** model where: -- Users belong to multiple teams -- Resources are scoped to teams -- Access control is team-based -- Data isolation is enforced at the database level - -### 2. Deployment Philosophy -- **Docker-first** approach for all deployments -- **Zero-downtime** deployments with health checks -- **Git-based** workflows with webhook integration -- **Multi-server** support with SSH connections - -### 3. Technology Stack -- **Backend**: Laravel 12 + PHP 8.4 -- **Frontend**: Livewire 3.5 + Alpine.js + Tailwind CSS 4.1 -- **Database**: PostgreSQL 15 + Redis 7 -- **Containerization**: Docker + Docker Compose -- **Testing**: Pest PHP 3.8 + Laravel Dusk - -### 4. Security Model -- **Defense-in-depth** security architecture -- **OAuth integration** with multiple providers -- **API token** authentication with Sanctum -- **Encrypted storage** for sensitive data -- **SSH key** management for server access - -## Development Quick Start - -### Local Setup -```bash -# Clone and setup -git clone https://github.com/coollabsio/coolify.git -cd coolify -cp .env.example .env - -# Docker development (recommended) -docker-compose -f docker-compose.dev.yml up -d -docker-compose exec app composer install -docker-compose exec app npm install -docker-compose exec app php artisan migrate -``` - -### Code Quality -```bash -# PHP code style -./vendor/bin/pint - -# Static analysis -./vendor/bin/phpstan analyse - -# Run tests -./vendor/bin/pest -``` - -## Common Patterns - -### Livewire Components -```php -class ApplicationShow extends Component -{ - public Application $application; - - protected $listeners = [ - 'deployment.started' => 'refresh', - 'deployment.completed' => 'refresh', - ]; - - public function deploy(): void - { - $this->authorize('deploy', $this->application); - app(ApplicationDeploymentService::class)->deploy($this->application); - } -} -``` - -### API Controllers -```php -class ApplicationController extends Controller -{ - public function __construct() - { - $this->middleware('auth:sanctum'); - $this->middleware('team.access'); - } - - public function deploy(Application $application): JsonResponse - { - $this->authorize('deploy', $application); - $deployment = app(ApplicationDeploymentService::class)->deploy($application); - return response()->json(['deployment_id' => $deployment->id]); - } -} -``` - -### Queue Jobs -```php -class DeployApplicationJob implements ShouldQueue -{ - public function handle(DockerService $dockerService): void - { - $this->deployment->update(['status' => 'running']); - - try { - $dockerService->deployContainer($this->deployment->application); - $this->deployment->update(['status' => 'success']); - } catch (Exception $e) { - $this->deployment->update(['status' => 'failed']); - throw $e; - } - } -} -``` - -## Testing Patterns - -### Feature Tests -```php -test('user can deploy application via API', function () { - $user = User::factory()->create(); - $application = Application::factory()->create(['team_id' => $user->currentTeam->id]); - - $response = $this->actingAs($user) - ->postJson("/api/v1/applications/{$application->id}/deploy"); - - $response->assertStatus(200); - expect($application->deployments()->count())->toBe(1); -}); -``` - -### Browser Tests -```php -test('user can create application through UI', function () { - $user = User::factory()->create(); - - $this->browse(function (Browser $browser) use ($user) { - $browser->loginAs($user) - ->visit('/applications/create') - ->type('name', 'Test App') - ->press('Create Application') - ->assertSee('Application created successfully'); - }); -}); -``` - -## Security Considerations - -### Authentication -- Multi-provider OAuth support -- API token authentication -- Team-based access control -- Session management - -### Data Protection -- Encrypted environment variables -- Secure SSH key storage -- Input validation and sanitization -- SQL injection prevention - -### Container Security -- Non-root container users -- Minimal capabilities -- Read-only filesystems -- Network isolation - -## Performance Optimization - -### Database -- Eager loading relationships -- Query optimization -- Connection pooling -- Caching strategies - -### Frontend -- Lazy loading components -- Asset optimization -- CDN integration -- Real-time updates via WebSockets - -## Contributing Guidelines - -### Code Standards -- PSR-12 PHP coding standards -- Laravel best practices -- Comprehensive test coverage -- Security-first approach - -### Pull Request Process -1. Fork repository -2. Create feature branch -3. Implement with tests -4. Run quality checks -5. Submit PR with clear description - -## Useful Commands - -### Development -```bash -# Start development environment -docker-compose -f docker-compose.dev.yml up -d - -# Run tests -./vendor/bin/pest - -# Code formatting -./vendor/bin/pint - -# Frontend development -npm run dev -``` - -### Production -```bash -# Install Coolify -curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash - -# Update Coolify -./scripts/upgrade.sh -``` - -## Resources - -### Documentation -- **[README.md](mdc:README.md)** - Project overview and installation -- **[CONTRIBUTING.md](mdc:CONTRIBUTING.md)** - Contribution guidelines -- **[CHANGELOG.md](mdc:CHANGELOG.md)** - Release history -- **[TECH_STACK.md](mdc:TECH_STACK.md)** - Technology overview - -### Configuration -- **[config/](mdc:config)** - Laravel configuration files -- **[database/migrations/](mdc:database/migrations)** - Database schema -- **[tests/](mdc:tests)** - Test suite - -This comprehensive rule set provides everything needed to understand, develop, and contribute to the Coolify project effectively. Each rule focuses on specific aspects while maintaining connections to the broader architecture. diff --git a/.cursor/rules/coolify-ai-docs.mdc b/.cursor/rules/coolify-ai-docs.mdc new file mode 100644 index 000000000..d99cc1692 --- /dev/null +++ b/.cursor/rules/coolify-ai-docs.mdc @@ -0,0 +1,156 @@ +--- +title: Coolify AI Documentation +description: Master reference to all Coolify AI documentation in .ai/ directory +globs: **/* +alwaysApply: true +--- + +# Coolify AI Documentation + +All Coolify AI documentation has been consolidated in the **`.ai/`** directory for better organization and single source of truth. + +## Quick Start + +- **For Claude Code**: Start with `CLAUDE.md` in the root directory +- **For Cursor IDE**: Start with `.ai/README.md` for navigation +- **For All AI Tools**: Browse `.ai/` directory by topic + +## Documentation Structure + +All detailed documentation lives in `.ai/` with the following organization: + +### 📚 Core Documentation +- **[Technology Stack](.ai/core/technology-stack.md)** - All versions, packages, dependencies (SINGLE SOURCE OF TRUTH for versions) +- **[Project Overview](.ai/core/project-overview.md)** - What Coolify is, high-level architecture +- **[Application Architecture](.ai/core/application-architecture.md)** - System design, components, relationships +- **[Deployment Architecture](.ai/core/deployment-architecture.md)** - Deployment flows, Docker, proxies + +### 💻 Development +- **[Development Workflow](.ai/development/development-workflow.md)** - Dev setup, commands, daily workflows +- **[Testing Patterns](.ai/development/testing-patterns.md)** - How to write/run tests, Docker requirements +- **[Laravel Boost](.ai/development/laravel-boost.md)** - Laravel-specific guidelines (SINGLE SOURCE for Laravel Boost) + +### 🎨 Code Patterns +- **[Database Patterns](.ai/patterns/database-patterns.md)** - Eloquent, migrations, relationships +- **[Frontend Patterns](.ai/patterns/frontend-patterns.md)** - Livewire, Alpine.js, Tailwind CSS +- **[Security Patterns](.ai/patterns/security-patterns.md)** - Auth, authorization, security +- **[Form Components](.ai/patterns/form-components.md)** - Enhanced forms with authorization +- **[API & Routing](.ai/patterns/api-and-routing.md)** - API design, routing conventions + +### 📖 Meta +- **[Maintaining Docs](.ai/meta/maintaining-docs.md)** - How to update/improve documentation +- **[Sync Guide](.ai/meta/sync-guide.md)** - Keeping docs synchronized + +## Quick Decision Tree + +**What are you working on?** + +### Running Commands +→ `.ai/development/development-workflow.md` +- `npm run dev` / `npm run build` - Frontend +- `php artisan serve` / `php artisan migrate` - Backend +- `docker exec coolify php artisan test` - Feature tests (requires Docker) +- `./vendor/bin/pest tests/Unit` - Unit tests (no Docker needed) +- `./vendor/bin/pint` - Code formatting + +### Writing Tests +→ `.ai/development/testing-patterns.md` +- **Unit tests**: No database, use mocking, run outside Docker +- **Feature tests**: Can use database, MUST run inside Docker +- Critical: Docker execution requirements prevent database connection errors + +### Building UI +→ `.ai/patterns/frontend-patterns.md` + `.ai/patterns/form-components.md` +- Livewire 3.5.20 with server-side state +- Alpine.js for client interactions +- Tailwind CSS 4.1.4 styling +- Form components with `canGate` authorization + +### Database Work +→ `.ai/patterns/database-patterns.md` +- Eloquent ORM patterns +- Migration best practices +- Relationship definitions +- Query optimization + +### Security & Authorization +→ `.ai/patterns/security-patterns.md` + `.ai/patterns/form-components.md` +- Team-based access control +- Policy and gate patterns +- Form authorization (`canGate`, `canResource`) +- API security with Sanctum + +### Laravel-Specific +→ `.ai/development/laravel-boost.md` +- Laravel 12.4.1 patterns +- Livewire 3 best practices +- Pest testing patterns +- Laravel conventions + +### Version Numbers +→ `.ai/core/technology-stack.md` +- **SINGLE SOURCE OF TRUTH** for all version numbers +- Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4, etc. +- Never duplicate versions - always reference this file + +## Critical Patterns (Always Follow) + +### Testing Commands +```bash +# Unit tests (no database, outside Docker) +./vendor/bin/pest tests/Unit + +# Feature tests (requires database, inside Docker) +docker exec coolify php artisan test +``` + +**NEVER** run Feature tests outside Docker - they will fail with database connection errors. + +### Form Authorization +ALWAYS include authorization on form components: +```blade + +``` + +### Livewire Components +MUST have exactly ONE root element. No exceptions. + +### Version Numbers +Use exact versions from `technology-stack.md`: +- ✅ Laravel 12.4.1 +- ❌ Laravel 12 or "v12" + +### Code Style +```bash +# Always run before committing +./vendor/bin/pint +``` + +## For AI Assistants + +### Important Notes +1. **Single Source of Truth**: Each piece of information exists in ONE location only +2. **Cross-Reference, Don't Duplicate**: Link to other files instead of copying content +3. **Version Precision**: Always use exact versions from `technology-stack.md` +4. **Docker for Feature Tests**: This is non-negotiable for database-dependent tests +5. **Form Authorization**: Security requirement, not optional + +### When to Use Which File +- **Quick commands**: `CLAUDE.md` or `development-workflow.md` +- **Detailed patterns**: Topic-specific files in `.ai/patterns/` +- **Testing**: `.ai/development/testing-patterns.md` +- **Laravel specifics**: `.ai/development/laravel-boost.md` +- **Versions**: `.ai/core/technology-stack.md` + +## Maintaining Documentation + +When updating documentation: +1. Read `.ai/meta/maintaining-docs.md` first +2. Follow single source of truth principle +3. Update cross-references when moving content +4. Test all links work +5. See `.ai/meta/sync-guide.md` for sync guidelines + +## Migration Note + +This file replaces all previous `.cursor/rules/*.mdc` files. All content has been migrated to `.ai/` directory for better organization and to serve as single source of truth for all AI tools (Claude Code, Cursor IDE, etc.). diff --git a/.cursor/rules/cursor_rules.mdc b/.cursor/rules/cursor_rules.mdc deleted file mode 100644 index 9edccd496..000000000 --- a/.cursor/rules/cursor_rules.mdc +++ /dev/null @@ -1,59 +0,0 @@ ---- -description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness. -globs: .cursor/rules/*.mdc -alwaysApply: true ---- - -# Cursor Rules Maintenance Guide - -> **Important**: These rules in `.cursor/rules/` are shared between Cursor IDE and other AI assistants. Changes here should be reflected in **[CLAUDE.md](mdc:CLAUDE.md)** when they affect core workflows or patterns. -> -> **Synchronization Guide**: See **[.AI_INSTRUCTIONS_SYNC.md](mdc:.AI_INSTRUCTIONS_SYNC.md)** for detailed guidelines on maintaining consistency between CLAUDE.md and .cursor/rules/. - -- **Required Rule Structure:** - ```markdown - --- - description: Clear, one-line description of what the rule enforces - globs: path/to/files/*.ext, other/path/**/* - alwaysApply: boolean - --- - - - **Main Points in Bold** - - Sub-points with details - - Examples and explanations - ``` - -- **File References:** - - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files - - Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references - - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references - -- **Code Examples:** - - Use language-specific code blocks - ```typescript - // ✅ DO: Show good examples - const goodExample = true; - - // ❌ DON'T: Show anti-patterns - const badExample = false; - ``` - -- **Rule Content Guidelines:** - - Start with high-level overview - - Include specific, actionable requirements - - Show examples of correct implementation - - Reference existing code when possible - - Keep rules DRY by referencing other rules - -- **Rule Maintenance:** - - Update rules when new patterns emerge - - Add examples from actual codebase - - Remove outdated patterns - - Cross-reference related rules - -- **Best Practices:** - - Use bullet points for clarity - - Keep descriptions concise - - Include both DO and DON'T examples - - Reference actual code over theoretical examples - - Use consistent formatting across rules \ No newline at end of file diff --git a/.cursor/rules/dev_workflow.mdc b/.cursor/rules/dev_workflow.mdc deleted file mode 100644 index 003251d8a..000000000 --- a/.cursor/rules/dev_workflow.mdc +++ /dev/null @@ -1,219 +0,0 @@ ---- -description: Guide for using Task Master to manage task-driven development workflows -globs: **/* -alwaysApply: true ---- -# Task Master Development Workflow - -This guide outlines the typical process for using Task Master to manage software development projects. - -## Primary Interaction: MCP Server vs. CLI - -Task Master offers two primary ways to interact: - -1. **MCP Server (Recommended for Integrated Tools)**: - - For AI agents and integrated development environments (like Cursor), interacting via the **MCP server is the preferred method**. - - The MCP server exposes Task Master functionality through a set of tools (e.g., `get_tasks`, `add_subtask`). - - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing. - - Refer to [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for details on the MCP architecture and available tools. - - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc). - - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change. - -2. **`task-master` CLI (For Users & Fallback)**: - - The global `task-master` command provides a user-friendly interface for direct terminal interaction. - - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP. - - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`. - - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`). - - Refer to [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc) for a detailed command reference. - -## Standard Development Workflow Process - -- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input=''` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to generate initial tasks.json -- Begin coding sessions with `get_tasks` / `task-master list` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to see current tasks, status, and IDs -- Determine the next task to work on using `next_task` / `task-master next` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). -- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) before breaking down tasks -- Review complexity report using `complexity_report` / `task-master complexity-report` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). -- Select tasks based on dependencies (all marked 'done'), priority level, and ID order -- Clarify tasks by checking task files in tasks/ directory or asking for user input -- View specific task details using `get_task` / `task-master show ` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to understand implementation requirements -- Break down complex tasks using `expand_task` / `task-master expand --id= --force --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) with appropriate flags like `--force` (to replace existing subtasks) and `--research`. -- Clear existing subtasks if needed using `clear_subtasks` / `task-master clear-subtasks --id=` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) before regenerating -- Implement code following task details, dependencies, and project standards -- Verify tasks according to test strategies before marking as complete (See [`tests.mdc`](mdc:.cursor/rules/tests.mdc)) -- Mark completed tasks with `set_task_status` / `task-master set-status --id= --status=done` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) -- Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from= --prompt="..."` or `update_task` / `task-master update-task --id= --prompt="..."` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) -- Add new tasks discovered during implementation using `add_task` / `task-master add-task --prompt="..." --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). -- Add new subtasks as needed using `add_subtask` / `task-master add-subtask --parent= --title="..."` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). -- Append notes or details to subtasks using `update_subtask` / `task-master update-subtask --id= --prompt='Add implementation notes here...\nMore details...'` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)). -- Generate task files with `generate` / `task-master generate` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) after updating tasks.json -- Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) when needed -- Respect dependency chains and task priorities when selecting work -- Report progress regularly using `get_tasks` / `task-master list` - -## Task Complexity Analysis - -- Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) for comprehensive analysis -- Review complexity report via `complexity_report` / `task-master complexity-report` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) for a formatted, readable version. -- Focus on tasks with highest complexity scores (8-10) for detailed breakdown -- Use analysis results to determine appropriate subtask allocation -- Note that reports are automatically used by the `expand_task` tool/command - -## Task Breakdown Process - -- Use `expand_task` / `task-master expand --id=`. It automatically uses the complexity report if found, otherwise generates default number of subtasks. -- Use `--num=` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations. -- Add `--research` flag to leverage Perplexity AI for research-backed expansion. -- Add `--force` flag to clear existing subtasks before generating new ones (default is to append). -- Use `--prompt=""` to provide additional context when needed. -- Review and adjust generated subtasks as necessary. -- Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`. -- If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=`. - -## Implementation Drift Handling - -- When implementation differs significantly from planned approach -- When future tasks need modification due to current implementation choices -- When new dependencies or requirements emerge -- Use `update` / `task-master update --from= --prompt='\nUpdate context...' --research` to update multiple future tasks. -- Use `update_task` / `task-master update-task --id= --prompt='\nUpdate context...' --research` to update a single specific task. - -## Task Status Management - -- Use 'pending' for tasks ready to be worked on -- Use 'done' for completed and verified tasks -- Use 'deferred' for postponed tasks -- Add custom status values as needed for project-specific workflows - -## Task Structure Fields - -- **id**: Unique identifier for the task (Example: `1`, `1.1`) -- **title**: Brief, descriptive title (Example: `"Initialize Repo"`) -- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) -- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) -- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`) - - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) - - This helps quickly identify which prerequisite tasks are blocking work -- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) -- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) -- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) -- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) -- Refer to task structure details (previously linked to `tasks.mdc`). - -## Configuration Management (Updated) - -Taskmaster configuration is managed through two main mechanisms: - -1. **`.taskmasterconfig` File (Primary):** - * Located in the project root directory. - * Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc. - * **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing. - * **View/Set specific models via `task-master models` command or `models` MCP tool.** - * Created automatically when you run `task-master models --setup` for the first time. - -2. **Environment Variables (`.env` / `mcp.json`):** - * Used **only** for sensitive API keys and specific endpoint URLs. - * Place API keys (one per provider) in a `.env` file in the project root for CLI usage. - * For MCP/Cursor integration, configure these keys in the `env` section of `.cursor/mcp.json`. - * Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.mdc`). - -**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool. -**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.cursor/mcp.json`. -**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project. - -## Determining the Next Task - -- Run `next_task` / `task-master next` to show the next task to work on. -- The command identifies tasks with all dependencies satisfied -- Tasks are prioritized by priority level, dependency count, and ID -- The command shows comprehensive task information including: - - Basic task details and description - - Implementation details - - Subtasks (if they exist) - - Contextual suggested actions -- Recommended before starting any new development work -- Respects your project's dependency structure -- Ensures tasks are completed in the appropriate sequence -- Provides ready-to-use commands for common task actions - -## Viewing Specific Task Details - -- Run `get_task` / `task-master show ` to view a specific task. -- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) -- Displays comprehensive information similar to the next command, but for a specific task -- For parent tasks, shows all subtasks and their current status -- For subtasks, shows parent task information and relationship -- Provides contextual suggested actions appropriate for the specific task -- Useful for examining task details before implementation or checking status - -## Managing Task Dependencies - -- Use `add_dependency` / `task-master add-dependency --id= --depends-on=` to add a dependency. -- Use `remove_dependency` / `task-master remove-dependency --id= --depends-on=` to remove a dependency. -- The system prevents circular dependencies and duplicate dependency entries -- Dependencies are checked for existence before being added or removed -- Task files are automatically regenerated after dependency changes -- Dependencies are visualized with status indicators in task listings and files - -## Iterative Subtask Implementation - -Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: - -1. **Understand the Goal (Preparation):** - * Use `get_task` / `task-master show ` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to thoroughly understand the specific goals and requirements of the subtask. - -2. **Initial Exploration & Planning (Iteration 1):** - * This is the first attempt at creating a concrete implementation plan. - * Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification. - * Determine the intended code changes (diffs) and their locations. - * Gather *all* relevant details from this exploration phase. - -3. **Log the Plan:** - * Run `update_subtask` / `task-master update-subtask --id= --prompt=''`. - * Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`. - -4. **Verify the Plan:** - * Run `get_task` / `task-master show ` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details. - -5. **Begin Implementation:** - * Set the subtask status using `set_task_status` / `task-master set-status --id= --status=in-progress`. - * Start coding based on the logged plan. - -6. **Refine and Log Progress (Iteration 2+):** - * As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches. - * **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy. - * **Regularly** use `update_subtask` / `task-master update-subtask --id= --prompt='\n- What worked...\n- What didn't work...'` to append new findings. - * **Crucially, log:** - * What worked ("fundamental truths" discovered). - * What didn't work and why (to avoid repeating mistakes). - * Specific code snippets or configurations that were successful. - * Decisions made, especially if confirmed with user input. - * Any deviations from the initial plan and the reasoning. - * The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors. - -7. **Review & Update Rules (Post-Implementation):** - * Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history. - * Identify any new or modified code patterns, conventions, or best practices established during the implementation. - * Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.mdc` and `self_improve.mdc`). - -8. **Mark Task Complete:** - * After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id= --status=done`. - -9. **Commit Changes (If using Git):** - * Stage the relevant code changes and any updated/new rule files (`git add .`). - * Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments. - * Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask \n\n- Details about changes...\n- Updated rule Y for pattern Z'`). - * Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.mdc`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one. - -10. **Proceed to Next Subtask:** - * Identify the next subtask (e.g., using `next_task` / `task-master next`). - -## Code Analysis & Refactoring Techniques - -- **Top-Level Function Search**: - - Useful for understanding module structure or planning refactors. - - Use grep/ripgrep to find exported functions/constants: - `rg "export (async function|function|const) \w+"` or similar patterns. - - Can help compare functions between files during migrations or identify potential naming conflicts. - ---- -*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.* \ No newline at end of file diff --git a/.cursor/rules/self_improve.mdc b/.cursor/rules/self_improve.mdc deleted file mode 100644 index 2bebaec75..000000000 --- a/.cursor/rules/self_improve.mdc +++ /dev/null @@ -1,59 +0,0 @@ ---- -description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices. -globs: **/* -alwaysApply: true ---- - -- **Rule Improvement Triggers:** - - New code patterns not covered by existing rules - - Repeated similar implementations across files - - Common error patterns that could be prevented - - New libraries or tools being used consistently - - Emerging best practices in the codebase - -- **Analysis Process:** - - Compare new code with existing rules - - Identify patterns that should be standardized - - Look for references to external documentation - - Check for consistent error handling patterns - - Monitor test patterns and coverage - -- **Rule Updates:** - - **Add New Rules When:** - - A new technology/pattern is used in 3+ files - - Common bugs could be prevented by a rule - - Code reviews repeatedly mention the same feedback - - New security or performance patterns emerge - - - **Modify Existing Rules When:** - - Better examples exist in the codebase - - Additional edge cases are discovered - - Related rules have been updated - - Implementation details have changed - - -- **Rule Quality Checks:** - - Rules should be actionable and specific - - Examples should come from actual code - - References should be up to date - - Patterns should be consistently enforced - -- **Continuous Improvement:** - - Monitor code review comments - - Track common development questions - - Update rules after major refactors - - Add links to relevant documentation - - Cross-reference related rules - -- **Rule Deprecation:** - - Mark outdated patterns as deprecated - - Remove rules that no longer apply - - Update references to deprecated rules - - Document migration paths for old patterns - -- **Documentation Updates:** - - Keep examples synchronized with code - - Update references to external docs - - Maintain links between related rules - - Document breaking changes -Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure. diff --git a/CLAUDE.md b/CLAUDE.md index 6434ef877..b7c496e42 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,9 +2,9 @@ # CLAUDE.md This file provides guidance to **Claude Code** (claude.ai/code) when working with code in this repository. -> **Note for AI Assistants**: This file is specifically for Claude Code. If you're using Cursor IDE, refer to the `.cursor/rules/` directory for detailed rule files. Both systems share core principles but are optimized for their respective workflows. +> **Note for AI Assistants**: This file is specifically for Claude Code. All detailed documentation is in the `.ai/` directory. Both Claude Code and Cursor IDE use the same source files in `.ai/` for consistency. > -> **Maintaining Instructions**: When updating AI instructions, see [.AI_INSTRUCTIONS_SYNC.md](.AI_INSTRUCTIONS_SYNC.md) for synchronization guidelines between CLAUDE.md and .cursor/rules/. +> **Maintaining Instructions**: When updating AI instructions, see [.ai/meta/sync-guide.md](.ai/meta/sync-guide.md) and [.ai/meta/maintaining-docs.md](.ai/meta/maintaining-docs.md) for guidelines. ## Project Overview @@ -27,7 +27,8 @@ ### Backend Development ### Code Quality - `./vendor/bin/pint` - Run Laravel Pint for code formatting - `./vendor/bin/phpstan` - Run PHPStan for static analysis -- `./vendor/bin/pest` - Run Pest tests (unit tests only, without database) +- `./vendor/bin/pest tests/Unit` - Run unit tests only (no database, can run outside Docker) +- `./vendor/bin/pest` - Run ALL tests (includes Feature tests, may require database) ### Running Tests **IMPORTANT**: Tests that require database connections MUST be run inside the Docker container: @@ -39,12 +40,14 @@ ### Running Tests ## Architecture Overview ### Technology Stack -- **Backend**: Laravel 12 (PHP 8.4) -- **Frontend**: Livewire 3.5+ with Alpine.js and Tailwind CSS 4.1+ +- **Backend**: Laravel 12.4.1 (PHP 8.4.7) +- **Frontend**: Livewire 3.5.20 with Alpine.js and Tailwind CSS 4.1.4 - **Database**: PostgreSQL 15 (primary), Redis 7 (cache/queues) - **Real-time**: Soketi (WebSocket server) - **Containerization**: Docker & Docker Compose -- **Queue Management**: Laravel Horizon +- **Queue Management**: Laravel Horizon 5.30.3 + +> **Note**: For complete version information and all dependencies, see [.ai/core/technology-stack.md](.ai/core/technology-stack.md) ### Key Components @@ -256,453 +259,61 @@ ## Important Reminders ## Additional Documentation -This file contains high-level guidelines for Claude Code. For **more detailed, topic-specific documentation**, refer to the `.cursor/rules/` directory (also accessible by Cursor IDE and other AI assistants): +This file contains high-level guidelines for Claude Code. For **more detailed, topic-specific documentation**, refer to the `.ai/` directory: -> **Cross-Reference**: The `.cursor/rules/` directory contains comprehensive, detailed documentation organized by topic. Start with [.cursor/rules/README.mdc](.cursor/rules/README.mdc) for an overview, then explore specific topics below. +> **Documentation Hub**: The `.ai/` directory contains comprehensive, detailed documentation organized by topic. Start with [.ai/README.md](.ai/README.md) for navigation, then explore specific topics below. -### Architecture & Patterns -- [Application Architecture](.cursor/rules/application-architecture.mdc) - Detailed application structure -- [Deployment Architecture](.cursor/rules/deployment-architecture.mdc) - Deployment patterns and flows -- [Database Patterns](.cursor/rules/database-patterns.mdc) - Database design and query patterns -- [Frontend Patterns](.cursor/rules/frontend-patterns.mdc) - Livewire and Alpine.js patterns -- [API & Routing](.cursor/rules/api-and-routing.mdc) - API design and routing conventions +### Core Documentation +- [Technology Stack](.ai/core/technology-stack.md) - All versions, packages, and dependencies (single source of truth) +- [Project Overview](.ai/core/project-overview.md) - What Coolify is and how it works +- [Application Architecture](.ai/core/application-architecture.md) - System design and component relationships +- [Deployment Architecture](.ai/core/deployment-architecture.md) - How deployments work end-to-end -### Development & Security -- [Development Workflow](.cursor/rules/development-workflow.mdc) - Development best practices -- [Security Patterns](.cursor/rules/security-patterns.mdc) - Security implementation details -- [Form Components](.cursor/rules/form-components.mdc) - Enhanced form components with authorization -- [Testing Patterns](.cursor/rules/testing-patterns.mdc) - Testing strategies and examples +### Development Practices +- [Development Workflow](.ai/development/development-workflow.md) - Development setup, commands, and workflows +- [Testing Patterns](.ai/development/testing-patterns.md) - Testing strategies and examples (Docker requirements!) +- [Laravel Boost](.ai/development/laravel-boost.md) - Laravel-specific guidelines and best practices -### Project Information -- [Project Overview](.cursor/rules/project-overview.mdc) - High-level project structure -- [Technology Stack](.cursor/rules/technology-stack.mdc) - Detailed tech stack information -- [Cursor Rules Guide](.cursor/rules/cursor_rules.mdc) - How to maintain cursor rules +### Code Patterns +- [Database Patterns](.ai/patterns/database-patterns.md) - Eloquent, migrations, relationships +- [Frontend Patterns](.ai/patterns/frontend-patterns.md) - Livewire, Alpine.js, Tailwind CSS +- [Security Patterns](.ai/patterns/security-patterns.md) - Authentication, authorization, security +- [Form Components](.ai/patterns/form-components.md) - Enhanced form components with authorization +- [API & Routing](.ai/patterns/api-and-routing.md) - API design and routing conventions -=== +### Meta Documentation +- [Maintaining Docs](.ai/meta/maintaining-docs.md) - How to update and improve AI documentation +- [Sync Guide](.ai/meta/sync-guide.md) - Keeping documentation synchronized - -=== foundation rules === +## Laravel Boost Guidelines -# Laravel Boost Guidelines +> **Full Guidelines**: See [.ai/development/laravel-boost.md](.ai/development/laravel-boost.md) for complete Laravel Boost guidelines. -The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. +### Essential Laravel Patterns -## Foundational Context -This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. +- Use PHP 8.4 constructor property promotion and typed properties +- Follow PSR-12 (run `./vendor/bin/pint` before committing) +- Use Eloquent ORM, avoid raw queries +- Use Form Request classes for validation +- Queue heavy operations with Laravel Horizon +- Never use `env()` outside config files +- Use named routes with `route()` function +- Laravel 12 with Laravel 10 structure (no bootstrap/app.php) -- php - 8.4.7 -- laravel/fortify (FORTIFY) - v1 -- laravel/framework (LARAVEL) - v12 -- laravel/horizon (HORIZON) - v5 -- laravel/prompts (PROMPTS) - v0 -- laravel/sanctum (SANCTUM) - v4 -- laravel/socialite (SOCIALITE) - v5 -- livewire/livewire (LIVEWIRE) - v3 -- laravel/dusk (DUSK) - v8 -- laravel/pint (PINT) - v1 -- laravel/telescope (TELESCOPE) - v5 -- pestphp/pest (PEST) - v3 -- phpunit/phpunit (PHPUNIT) - v11 -- rector/rector (RECTOR) - v2 -- laravel-echo (ECHO) - v2 -- tailwindcss (TAILWINDCSS) - v4 -- vue (VUE) - v3 +### Testing Requirements +- **Unit tests**: No database, use mocking, run with `./vendor/bin/pest tests/Unit` +- **Feature tests**: Can use database, run with `docker exec coolify php artisan test` +- Every change must have tests +- Use Pest for all tests -## Conventions -- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. -- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. -- Check for existing components to reuse before writing a new one. +### Livewire & Frontend -## Verification Scripts -- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. - -## Application Structure & Architecture -- Stick to existing directory structure - don't create new base folders without approval. -- Do not change the application's dependencies without approval. - -## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. - -## Replies -- Be concise in your explanations - focus on what's important rather than explaining obvious details. - -## Documentation Files -- You must only create documentation files if explicitly requested by the user. - - -=== boost rules === - -## Laravel Boost -- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. - -## Artisan -- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters. - -## URLs -- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port. - -## Tinker / Debugging -- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. -- Use the `database-query` tool when you only need to read from the database. - -## Reading Browser Logs With the `browser-logs` Tool -- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. -- Only recent browser logs will be useful - ignore old logs. - -## Searching Documentation (Critically Important) -- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. -- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. -- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches. -- Search the documentation before making code changes to ensure we are taking the correct approach. -- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. -- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. - -### Available Search Syntax -- You can and should pass multiple queries at once. The most relevant results will be returned first. - -1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth' -2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit" -3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order -4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit" -5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms - - -=== php rules === - -## PHP - -- Always use curly braces for control structures, even if it has one line. - -### Constructors -- Use PHP 8 constructor property promotion in `__construct()`. - - public function __construct(public GitHub $github) { } -- Do not allow empty `__construct()` methods with zero parameters. - -### Type Declarations -- Always use explicit return type declarations for methods and functions. -- Use appropriate PHP type hints for method parameters. - - -protected function isAccessible(User $user, ?string $path = null): bool -{ - ... -} - - -## Comments -- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on. - -## PHPDoc Blocks -- Add useful array shape type definitions for arrays when appropriate. - -## Enums -- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. - - -=== laravel/core rules === - -## Do Things the Laravel Way - -- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `artisan make:class`. -- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. - -### Database -- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. -- Use Eloquent models and relationships before suggesting raw database queries -- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. -- Generate code that prevents N+1 query problems by using eager loading. -- Use Laravel's query builder for very complex database operations. - -### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. - -### APIs & Eloquent Resources -- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. - -### Controllers & Validation -- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. -- Check sibling Form Requests to see if the application uses array or string based validation rules. - -### Queues -- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. - -### Authentication & Authorization -- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). - -### URL Generation -- When generating links to other pages, prefer named routes and the `route()` function. - -### Configuration -- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. - -### Testing -- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. -- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `php artisan make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. - -### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. - - -=== laravel/v12 rules === - -## Laravel 12 - -- Use the `search-docs` tool to get version specific documentation. -- This project upgraded from Laravel 10 without migrating to the new streamlined Laravel file structure. -- This is **perfectly fine** and recommended by Laravel. Follow the existing structure from Laravel 10. We do not to need migrate to the new Laravel structure unless the user explicitly requests that. - -### Laravel 10 Structure -- Middleware typically lives in `app/Http/Middleware/` and service providers in `app/Providers/`. -- There is no `bootstrap/app.php` application configuration in a Laravel 10 structure: - - Middleware registration happens in `app/Http/Kernel.php` - - Exception handling is in `app/Exceptions/Handler.php` - - Console commands and schedule register in `app/Console/Kernel.php` - - Rate limits likely exist in `RouteServiceProvider` or `app/Http/Kernel.php` - -### Database -- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. -- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. - -### Models -- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. - - -=== livewire/core rules === - -## Livewire Core -- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components -- State should live on the server, with the UI reflecting it. -- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. - -## Livewire Best Practices -- Livewire components require a single root element. -- Use `wire:loading` and `wire:dirty` for delightful loading states. -- Add `wire:key` in loops: - - ```blade - @foreach ($items as $item) -
- {{ $item->name }} -
- @endforeach - ``` - -- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects: - - - public function mount(User $user) { $this->user = $user; } - public function updatedSearch() { $this->resetPage(); } - - - -## Testing Livewire - - - Livewire::test(Counter::class) - ->assertSet('count', 0) - ->call('increment') - ->assertSet('count', 1) - ->assertSee(1) - ->assertStatus(200); - - - - - $this->get('/posts/create') - ->assertSeeLivewire(CreatePost::class); - - - -=== livewire/v3 rules === - -## Livewire 3 - -### Key Changes From Livewire 2 -- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions. - - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. - - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). - - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). - - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). - -### New Directives -- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples. - -### Alpine -- Alpine is now included with Livewire, don't manually include Alpine.js. -- Plugins included with Alpine: persist, intersect, collapse, and focus. - -### Lifecycle Hooks -- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring: - - -document.addEventListener('livewire:init', function () { - Livewire.hook('request', ({ fail }) => { - if (fail && fail.status === 419) { - alert('Your session expired'); - } - }); - - Livewire.hook('message.failed', (message, component) => { - console.error(message); - }); -}); - - - -=== pint/core rules === - -## Laravel Pint Code Formatter - -- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. - - -=== pest/core rules === - -## Pest - -### Testing -- If you need to verify a feature is working, write or update a Unit / Feature test. - -### Pest Tests -- All tests must be written using Pest. Use `php artisan make:test --pest `. -- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. -- Tests should test all of the happy paths, failure paths, and weird paths. -- Tests live in the `tests/Feature` and `tests/Unit` directories. -- **Unit tests** MUST use mocking and avoid database. They can run outside Docker. -- **Feature tests** can use database but MUST run inside Docker container. -- **Design for testability**: Structure code to be testable without database when possible. Use dependency injection and interfaces. -- **Mock by default**: Prefer `Mockery::mock()` over `Model::factory()->create()` in unit tests. -- Pest tests look and behave like this: - -it('is true', function () { - expect(true)->toBeTrue(); -}); - - -### Running Tests -**IMPORTANT**: Always run tests in the correct environment based on database dependencies: - -**Unit Tests (no database):** -- Run outside Docker: `./vendor/bin/pest tests/Unit` -- Run specific file: `./vendor/bin/pest tests/Unit/ProxyCustomCommandsTest.php` -- These tests use mocking and don't require PostgreSQL - -**Feature Tests (with database):** -- Run inside Docker: `docker exec coolify php artisan test` -- Run specific file: `docker exec coolify php artisan test tests/Feature/ExampleTest.php` -- Filter by name: `docker exec coolify php artisan test --filter=testName` -- These tests require PostgreSQL and use factories/migrations - -**General Guidelines:** -- Run the minimal number of tests using an appropriate filter before finalizing code edits -- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite -- If you get database connection errors, you're running a Feature test outside Docker - move it inside - -### Pest Assertions -- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: - -it('returns all', function () { - $response = $this->postJson('/api/docs', []); - - $response->assertSuccessful(); -}); - - -### Mocking -- Mocking can be very helpful when appropriate. -- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. -- You can also create partial mocks using the same import or self method. - -### Datasets -- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules. - - -it('has emails', function (string $email) { - expect($email)->not->toBeEmpty(); -})->with([ - 'james' => 'james@laravel.com', - 'taylor' => 'taylor@laravel.com', -]); - - - -=== tailwindcss/core rules === - -## Tailwind Core - -- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own. -- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..) -- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically -- You can use the `search-docs` tool to get exact examples from the official documentation when needed. - -### Spacing -- When listing items, use gap utilities for spacing, don't use margins. - - -
-
Superior
-
Michigan
-
Erie
-
-
- - -### Dark Mode -- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. - - -=== tailwindcss/v4 rules === - -## Tailwind 4 - -- Always use Tailwind CSS v4 - do not use the deprecated utilities. -- `corePlugins` is not supported in Tailwind v4. -- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: - - - - -### Replaced Utilities -- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement. -- Opacity values are still numeric. - -| Deprecated | Replacement | -|------------+--------------| -| bg-opacity-* | bg-black/* | -| text-opacity-* | text-black/* | -| border-opacity-* | border-black/* | -| divide-opacity-* | divide-black/* | -| ring-opacity-* | ring-black/* | -| placeholder-opacity-* | placeholder-black/* | -| flex-shrink-* | shrink-* | -| flex-grow-* | grow-* | -| overflow-ellipsis | text-ellipsis | -| decoration-slice | box-decoration-slice | -| decoration-clone | box-decoration-clone | - - -=== tests rules === - -## Test Enforcement - -- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. -- **For Unit tests**: Use `./vendor/bin/pest tests/Unit/YourTest.php` (runs outside Docker) -- **For Feature tests**: Use `docker exec coolify php artisan test --filter=YourTest` (runs inside Docker) -- Choose the correct test type based on database dependency: - - No database needed? → Unit test with mocking - - Database needed? → Feature test in Docker -
+- Livewire components require single root element +- Use `wire:model.live` for real-time updates +- Alpine.js included with Livewire +- Tailwind CSS 4.1.4 (use new utilities, not deprecated ones) +- Use `gap` utilities for spacing, not margins Random other things you should remember: From 76e93806bfe749f62d927c1b5be79dcbead40bdb Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:28:56 +0100 Subject: [PATCH 51/95] docs: consolidate AI documentation into .ai/ directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update CLAUDE.md to reference .ai/ directory as single source of truth - Move documentation structure to organized .ai/ directory with core/, development/, patterns/, meta/ subdirectories - Update .ai/README.md with correct path references - Update .ai/meta/maintaining-docs.md to reflect new structure - Consolidate sync-guide.md with detailed synchronization rules - Fix cross-reference in frontend-patterns.md 🤖 Generated with Claude Code Co-Authored-By: Claude --- .ai/README.md | 6 +- .ai/meta/maintaining-docs.md | 3 +- .ai/meta/sync-guide.md | 208 +++++++++++++++++++----------- .ai/patterns/frontend-patterns.md | 2 +- 4 files changed, 139 insertions(+), 80 deletions(-) diff --git a/.ai/README.md b/.ai/README.md index 357de249d..da24b09dc 100644 --- a/.ai/README.md +++ b/.ai/README.md @@ -4,7 +4,7 @@ # Coolify AI Documentation ## Quick Start -- **For Claude Code**: Start with [CLAUDE.md](CLAUDE.md) +- **For Claude Code**: Start with [CLAUDE.md in root directory](../CLAUDE.md) - **For Cursor IDE**: Check `.cursor/rules/coolify-ai-docs.mdc` which references this directory - **For Other AI Tools**: Continue reading below @@ -92,7 +92,7 @@ ### Version Numbers ## Navigation Tips -1. **Start broad**: Begin with project-overview or CLAUDE.md +1. **Start broad**: Begin with project-overview or ../CLAUDE.md 2. **Get specific**: Navigate to topic-specific files for details 3. **Cross-reference**: Files link to related topics 4. **Single source**: Version numbers and critical data exist in ONE place only @@ -135,7 +135,7 @@ ## Contributing ## Questions? -- **Claude Code users**: Check [CLAUDE.md](CLAUDE.md) first +- **Claude Code users**: Check [../CLAUDE.md](../CLAUDE.md) first - **Cursor IDE users**: Check `.cursor/rules/coolify-ai-docs.mdc` - **Documentation issues**: See [meta/maintaining-docs.md](meta/maintaining-docs.md) - **Sync issues**: See [meta/sync-guide.md](meta/sync-guide.md) diff --git a/.ai/meta/maintaining-docs.md b/.ai/meta/maintaining-docs.md index 22e3c7c67..1a1552399 100644 --- a/.ai/meta/maintaining-docs.md +++ b/.ai/meta/maintaining-docs.md @@ -9,13 +9,14 @@ ## Documentation Structure ``` .ai/ ├── README.md # Navigation hub -├── CLAUDE.md # Main Claude Code instructions ├── core/ # Core project information ├── development/ # Development practices ├── patterns/ # Code patterns and best practices └── meta/ # Documentation maintenance guides ``` +> **Note**: `CLAUDE.md` is in the repository root, not in the `.ai/` directory. + ## Required File Structure When creating new documentation files: diff --git a/.ai/meta/sync-guide.md b/.ai/meta/sync-guide.md index bbe0a90e1..ab9a45d1a 100644 --- a/.ai/meta/sync-guide.md +++ b/.ai/meta/sync-guide.md @@ -4,153 +4,211 @@ # AI Instructions Synchronization Guide ## Overview -Coolify maintains AI instructions in two parallel systems: +Coolify maintains AI instructions with a **single source of truth** approach: -1. **CLAUDE.md** - For Claude Code (claude.ai/code) -2. **.cursor/rules/** - For Cursor IDE and other AI assistants +1. **CLAUDE.md** - Main entry point for Claude Code (references `.ai/` directory) +2. **.cursor/rules/coolify-ai-docs.mdc** - Master reference file for Cursor IDE (references `.ai/` directory) +3. **.ai/** - Single source of truth containing all detailed documentation -Both systems share core principles but are optimized for their respective workflows. +All AI tools (Claude Code, Cursor IDE, etc.) reference the same `.ai/` directory to ensure consistency. ## Structure -### CLAUDE.md -- **Purpose**: Condensed, workflow-focused guide for Claude Code +### CLAUDE.md (Root Directory) +- **Purpose**: Entry point for Claude Code with quick-reference guide - **Format**: Single markdown file - **Includes**: - Quick-reference development commands - High-level architecture overview - - Core patterns and guidelines - - Embedded Laravel Boost guidelines - - References to detailed .cursor/rules/ documentation + - Essential patterns and guidelines + - References to detailed `.ai/` documentation -### .cursor/rules/ -- **Purpose**: Detailed, topic-specific documentation -- **Format**: Multiple .mdc files organized by topic +### .cursor/rules/coolify-ai-docs.mdc +- **Purpose**: Master reference file for Cursor IDE +- **Format**: Single .mdc file with frontmatter +- **Content**: Quick decision tree and references to `.ai/` directory +- **Note**: Replaces all previous topic-specific .mdc files + +### .ai/ Directory (Single Source of Truth) +- **Purpose**: All detailed, topic-specific documentation +- **Format**: Organized markdown files by category - **Structure**: - - `README.mdc` - Main index and overview - - `cursor_rules.mdc` - Maintenance guidelines - - Topic-specific files (testing-patterns.mdc, security-patterns.mdc, etc.) -- **Used by**: Cursor IDE, Claude Code (for detailed reference), other AI assistants + ``` + .ai/ + ├── README.md # Navigation hub + ├── core/ # Project information + │ ├── technology-stack.md # Version numbers (SINGLE SOURCE OF TRUTH) + │ ├── project-overview.md + │ ├── application-architecture.md + │ └── deployment-architecture.md + ├── development/ # Development practices + │ ├── development-workflow.md + │ ├── testing-patterns.md + │ └── laravel-boost.md + ├── patterns/ # Code patterns + │ ├── database-patterns.md + │ ├── frontend-patterns.md + │ ├── security-patterns.md + │ ├── form-components.md + │ └── api-and-routing.md + └── meta/ # Documentation guides + ├── maintaining-docs.md + └── sync-guide.md (this file) + ``` +- **Used by**: All AI tools through CLAUDE.md or coolify-ai-docs.mdc ## Cross-References -Both systems reference each other: +All systems reference the `.ai/` directory as the source of truth: -- **CLAUDE.md** → references `.cursor/rules/` for detailed documentation -- **.cursor/rules/README.mdc** → references `CLAUDE.md` for Claude Code workflow -- **.cursor/rules/cursor_rules.mdc** → notes that changes should sync with CLAUDE.md +- **CLAUDE.md** → references `.ai/` files for detailed documentation +- **.cursor/rules/coolify-ai-docs.mdc** → references `.ai/` files for detailed documentation +- **.ai/README.md** → provides navigation to all documentation ## Maintaining Consistency -When updating AI instructions, follow these guidelines: - ### 1. Core Principles (MUST be consistent) -- Laravel version (currently Laravel 12) -- PHP version (8.4) + +These are defined ONCE in `.ai/core/technology-stack.md`: +- Laravel version (currently Laravel 12.4.1) +- PHP version (8.4.7) +- All package versions (Livewire 3.5.20, Tailwind 4.1.4, etc.) + +**Exception**: CLAUDE.md is permitted to show essential version numbers as a quick reference for convenience. These must stay synchronized with `technology-stack.md`. When updating versions, update both locations. + +Other critical patterns defined in `.ai/`: - Testing execution rules (Docker for Feature tests, mocking for Unit tests) - Security patterns and authorization requirements - Code style requirements (Pint, PSR-12) ### 2. Where to Make Changes +**For version numbers** (Laravel, PHP, packages): +1. Update `.ai/core/technology-stack.md` (single source of truth) +2. Update CLAUDE.md quick reference section (essential versions only) +3. Verify both files stay synchronized +4. Never duplicate version numbers in other locations + **For workflow changes** (how to run commands, development setup): -- Primary: `CLAUDE.md` -- Secondary: `.cursor/rules/development-workflow.mdc` +1. Update `.ai/development/development-workflow.md` +2. Update quick reference in CLAUDE.md if needed +3. Verify `.cursor/rules/coolify-ai-docs.mdc` references are correct **For architectural patterns** (how code should be structured): -- Primary: `.cursor/rules/` topic files -- Secondary: Reference in `CLAUDE.md` "Additional Documentation" section +1. Update appropriate file in `.ai/core/` +2. Add cross-references from related docs +3. Update CLAUDE.md if it needs to highlight this pattern + +**For code patterns** (how to write code): +1. Update appropriate file in `.ai/patterns/` +2. Add examples from real codebase +3. Cross-reference from related docs **For testing patterns**: -- Both: Must be synchronized -- `CLAUDE.md` - Contains condensed testing execution rules -- `.cursor/rules/testing-patterns.mdc` - Contains detailed examples and patterns +1. Update `.ai/development/testing-patterns.md` +2. Ensure CLAUDE.md testing section references it ### 3. Update Checklist When making significant changes: - [ ] Identify if change affects core principles (version numbers, critical patterns) -- [ ] Update primary location (CLAUDE.md or .cursor/rules/) -- [ ] Check if update affects cross-referenced content -- [ ] Update secondary location if needed -- [ ] Verify cross-references are still accurate -- [ ] Run: `./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc` (if applicable) +- [ ] Update primary location in `.ai/` directory +- [ ] Check if CLAUDE.md needs quick-reference update +- [ ] Verify `.cursor/rules/coolify-ai-docs.mdc` references are still accurate +- [ ] Update cross-references in related `.ai/` files +- [ ] Verify all relative paths work correctly +- [ ] Test links in markdown files +- [ ] Run: `./vendor/bin/pint` on modified files (if applicable) ### 4. Common Inconsistencies to Watch -- **Version numbers**: Laravel, PHP, package versions -- **Testing instructions**: Docker execution requirements -- **File paths**: Ensure relative paths work from root -- **Command syntax**: Docker commands, artisan commands -- **Architecture decisions**: Laravel 10 structure vs Laravel 12+ structure +- **Version numbers**: Should ONLY exist in `.ai/core/technology-stack.md` +- **Testing instructions**: Docker execution requirements must be consistent +- **File paths**: Ensure relative paths work from their location +- **Command syntax**: Docker commands, artisan commands must be accurate +- **Cross-references**: Links must point to current file locations ## File Organization ``` / -├── CLAUDE.md # Claude Code instructions (condensed) -├── .AI_INSTRUCTIONS_SYNC.md # This file -└── .cursor/ - └── rules/ - ├── README.mdc # Index and overview - ├── cursor_rules.mdc # Maintenance guide - ├── testing-patterns.mdc # Testing details - ├── development-workflow.mdc # Dev setup details - ├── security-patterns.mdc # Security details - ├── application-architecture.mdc - ├── deployment-architecture.mdc - ├── database-patterns.mdc - ├── frontend-patterns.mdc - ├── api-and-routing.mdc - ├── form-components.mdc - ├── technology-stack.mdc - ├── project-overview.mdc - └── laravel-boost.mdc # Laravel-specific patterns +├── CLAUDE.md # Claude Code entry point +├── .AI_INSTRUCTIONS_SYNC.md # Redirect to this file +├── .cursor/ +│ └── rules/ +│ └── coolify-ai-docs.mdc # Cursor IDE master reference +└── .ai/ # SINGLE SOURCE OF TRUTH + ├── README.md # Navigation hub + ├── core/ # Project information + ├── development/ # Development practices + ├── patterns/ # Code patterns + └── meta/ # Documentation guides ``` ## Recent Updates +### 2025-11-18 - Documentation Consolidation +- ✅ Consolidated all documentation into `.ai/` directory +- ✅ Created single source of truth for version numbers +- ✅ Reduced CLAUDE.md from 719 to 319 lines +- ✅ Replaced 11 .cursor/rules/*.mdc files with single coolify-ai-docs.mdc +- ✅ Organized by topic: core/, development/, patterns/, meta/ +- ✅ Standardized version numbers (Laravel 12.4.1, PHP 8.4.7, Tailwind 4.1.4) +- ✅ Created comprehensive navigation with .ai/README.md + ### 2025-10-07 - ✅ Added cross-references between CLAUDE.md and .cursor/rules/ - ✅ Synchronized Laravel version (12) across all files - ✅ Added comprehensive testing execution rules (Docker for Feature tests) - ✅ Added test design philosophy (prefer mocking over database) - ✅ Fixed inconsistencies in testing documentation -- ✅ Created this synchronization guide ## Maintenance Commands ```bash -# Check for version inconsistencies -grep -r "Laravel [0-9]" CLAUDE.md .cursor/rules/*.mdc +# Check for version inconsistencies (should only be in technology-stack.md) +# Note: CLAUDE.md is allowed to show quick reference versions +grep -r "Laravel 12" .ai/ CLAUDE.md .cursor/rules/coolify-ai-docs.mdc +grep -r "PHP 8.4" .ai/ CLAUDE.md .cursor/rules/coolify-ai-docs.mdc -# Check for PHP version consistency -grep -r "PHP [0-9]" CLAUDE.md .cursor/rules/*.mdc +# Check for broken cross-references to old .mdc files +grep -r "\.cursor/rules/.*\.mdc" .ai/ CLAUDE.md # Format all documentation -./vendor/bin/pint CLAUDE.md .cursor/rules/*.mdc +./vendor/bin/pint CLAUDE.md .ai/**/*.md # Search for specific patterns across all docs -grep -r "pattern_to_check" CLAUDE.md .cursor/rules/ +grep -r "pattern_to_check" CLAUDE.md .ai/ .cursor/rules/ + +# Verify all markdown links work (from repository root) +find .ai -name "*.md" -exec grep -H "\[.*\](.*)" {} \; ``` ## Contributing When contributing documentation: -1. Check both CLAUDE.md and .cursor/rules/ for existing documentation -2. Add to appropriate location(s) based on guidelines above -3. Add cross-references if creating new patterns -4. Update this file if changing organizational structure -5. Verify consistency before submitting PR +1. **Check `.ai/` directory** for existing documentation +2. **Update `.ai/` files** - this is the single source of truth +3. **Use cross-references** - never duplicate content +4. **Update CLAUDE.md** if adding critical quick-reference information +5. **Verify `.cursor/rules/coolify-ai-docs.mdc`** still references correctly +6. **Test all links** work from their respective locations +7. **Update this sync-guide.md** if changing organizational structure +8. **Verify consistency** before submitting PR ## Questions? If unsure about where to document something: -- **Quick reference / workflow** → CLAUDE.md -- **Detailed patterns / examples** → .cursor/rules/[topic].mdc -- **Both?** → Start with .cursor/rules/, then reference in CLAUDE.md +- **Version numbers** → `.ai/core/technology-stack.md` (ONLY location) +- **Quick reference / commands** → CLAUDE.md + `.ai/development/development-workflow.md` +- **Detailed patterns / examples** → `.ai/patterns/[topic].md` +- **Architecture / concepts** → `.ai/core/[topic].md` +- **Development practices** → `.ai/development/[topic].md` +- **Documentation guides** → `.ai/meta/[topic].md` -When in doubt, prefer detailed documentation in .cursor/rules/ and concise references in CLAUDE.md. +**Golden Rule**: Each piece of information exists in ONE location in `.ai/`, other files reference it. + +When in doubt, prefer detailed documentation in `.ai/` and lightweight references in CLAUDE.md and coolify-ai-docs.mdc. diff --git a/.ai/patterns/frontend-patterns.md b/.ai/patterns/frontend-patterns.md index ecd21a8d8..675881608 100644 --- a/.ai/patterns/frontend-patterns.md +++ b/.ai/patterns/frontend-patterns.md @@ -258,7 +258,7 @@ ### Benefits - **Automatic disabling** for unauthorized users - **Smart behavior** (disables instantSave on checkboxes for unauthorized users) -For complete documentation, see **[form-components.mdc](mdc:.cursor/rules/form-components.mdc)** +For complete documentation, see **[form-components.md](.ai/patterns/form-components.md)** ## Form Handling Patterns From f81640e316f3864bb0e40236c971d95e9aa9b04e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 18 Nov 2025 23:24:11 +0100 Subject: [PATCH 52/95] fix: correct status for services with all containers excluded from health checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When all services in a Docker Compose file have `exclude_from_hc: true`, the status aggregation logic was returning invalid states causing broken UI. **Problems fixed:** - ComplexStatusCheck returned 'running:healthy' for apps with no monitored containers - Service model returned ':' (null status) when all services excluded - UI showed active start/stop buttons for non-running services **Changes:** - ComplexStatusCheck: Return 'exited:healthy' when relevantContainerCount is 0 - Service model: Return 'exited:healthy' when both status and health are null - Added comprehensive unit tests to verify the fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/Actions/Shared/ComplexStatusCheck.php | 2 +- app/Models/Service.php | 5 ++ tests/Unit/ExcludeFromHealthCheckTest.php | 59 +++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/ExcludeFromHealthCheckTest.php diff --git a/app/Actions/Shared/ComplexStatusCheck.php b/app/Actions/Shared/ComplexStatusCheck.php index e06136e3c..fbaa8cae5 100644 --- a/app/Actions/Shared/ComplexStatusCheck.php +++ b/app/Actions/Shared/ComplexStatusCheck.php @@ -114,7 +114,7 @@ private function aggregateContainerStatuses($application, $containers) } if ($relevantContainerCount === 0) { - return 'running:healthy'; + return 'exited:healthy'; } if ($hasRestarting) { diff --git a/app/Models/Service.php b/app/Models/Service.php index ef755d105..15ee2d1bc 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -244,6 +244,11 @@ public function getStatusAttribute() } } + // If all services are excluded from status checks, return a default exited status + if ($complexStatus === null && $complexHealth === null) { + return 'exited:healthy'; + } + return "{$complexStatus}:{$complexHealth}"; } diff --git a/tests/Unit/ExcludeFromHealthCheckTest.php b/tests/Unit/ExcludeFromHealthCheckTest.php new file mode 100644 index 000000000..56da2e6c5 --- /dev/null +++ b/tests/Unit/ExcludeFromHealthCheckTest.php @@ -0,0 +1,59 @@ +toContain("if (\$relevantContainerCount === 0) {\n return 'exited:healthy';\n }") + ->not->toContain("if (\$relevantContainerCount === 0) {\n return 'running:healthy';\n }"); +}); + +it('ensures Service model returns exited status when all services excluded', function () { + $serviceModelFile = file_get_contents(__DIR__.'/../../app/Models/Service.php'); + + // Check that when all services are excluded from status checks, + // the Service model returns 'exited:healthy' instead of ':' (null:null) + expect($serviceModelFile) + ->toContain('// If all services are excluded from status checks, return a default exited status') + ->toContain("if (\$complexStatus === null && \$complexHealth === null) {\n return 'exited:healthy';\n }"); +}); + +it('ensures GetContainersStatus returns null when all containers excluded', function () { + $getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php'); + + // Check that when all containers are excluded, the aggregateApplicationStatus + // method returns null to avoid updating status + expect($getContainersStatusFile) + ->toContain('// If all containers are excluded, don\'t update status') + ->toContain("if (\$relevantStatuses->isEmpty()) {\n return null;\n }"); +}); + +it('ensures exclude_from_hc flag is properly checked in ComplexStatusCheck', function () { + $complexStatusCheckFile = file_get_contents(__DIR__.'/../../app/Actions/Shared/ComplexStatusCheck.php'); + + // Verify that exclude_from_hc is properly parsed from docker-compose + expect($complexStatusCheckFile) + ->toContain('$excludeFromHc = data_get($serviceConfig, \'exclude_from_hc\', false);') + ->toContain('if ($excludeFromHc || $restartPolicy === \'no\') {') + ->toContain('$excludedContainers->push($serviceName);'); +}); + +it('ensures exclude_from_hc flag is properly checked in GetContainersStatus', function () { + $getContainersStatusFile = file_get_contents(__DIR__.'/../../app/Actions/Docker/GetContainersStatus.php'); + + // Verify that exclude_from_hc is properly parsed from docker-compose + expect($getContainersStatusFile) + ->toContain('$excludeFromHc = data_get($serviceConfig, \'exclude_from_hc\', false);') + ->toContain('if ($excludeFromHc || $restartPolicy === \'no\') {') + ->toContain('$excludedContainers->push($serviceName);'); +}); From 498b189286c0c2dacacf9d90f9a3e7d8d9d6b4d1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:54:51 +0100 Subject: [PATCH 53/95] fix: correct status for services with all containers excluded from health checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When all containers are excluded from health checks, display their actual status with :excluded suffix instead of misleading hardcoded statuses. This prevents broken UI state with incorrect action buttons and provides clarity that monitoring is disabled. 🤖 Generated with Claude Code Co-Authored-By: Claude --- app/Actions/Docker/GetContainersStatus.php | 8 +- app/Actions/Shared/ComplexStatusCheck.php | 51 +++++++- app/Models/Service.php | 78 +++++++++++- .../components/status/services.blade.php | 25 ++-- .../project/service/heading.blade.php | 4 +- tests/Unit/ContainerHealthStatusTest.php | 116 ++++++++++++++++++ tests/Unit/ExcludeFromHealthCheckTest.php | 66 ++++++++-- 7 files changed, 317 insertions(+), 31 deletions(-) create mode 100644 tests/Unit/ContainerHealthStatusTest.php diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index c7f4055f0..ef5cc37aa 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -98,11 +98,13 @@ public function handle(Server $server, ?Collection $containers = null, ?Collecti $labels = data_get($container, 'Config.Labels'); } $containerStatus = data_get($container, 'State.Status'); - $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); + $containerHealth = data_get($container, 'State.Health.Status'); if ($containerStatus === 'restarting') { - $containerStatus = "restarting ($containerHealth)"; + $healthSuffix = $containerHealth ?? 'unknown'; + $containerStatus = "restarting ($healthSuffix)"; } else { - $containerStatus = "$containerStatus ($containerHealth)"; + $healthSuffix = $containerHealth ?? 'unknown'; + $containerStatus = "$containerStatus ($healthSuffix)"; } $labels = Arr::undot(format_docker_labels_to_json($labels)); $applicationId = data_get($labels, 'coolify.applicationId'); diff --git a/app/Actions/Shared/ComplexStatusCheck.php b/app/Actions/Shared/ComplexStatusCheck.php index fbaa8cae5..1013c73e0 100644 --- a/app/Actions/Shared/ComplexStatusCheck.php +++ b/app/Actions/Shared/ComplexStatusCheck.php @@ -97,14 +97,14 @@ private function aggregateContainerStatuses($application, $containers) $relevantContainerCount++; $containerStatus = data_get($container, 'State.Status'); - $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); + $containerHealth = data_get($container, 'State.Health.Status'); if ($containerStatus === 'restarting') { $hasRestarting = true; $hasUnhealthy = true; } elseif ($containerStatus === 'running') { $hasRunning = true; - if ($containerHealth === 'unhealthy') { + if ($containerHealth && $containerHealth === 'unhealthy') { $hasUnhealthy = true; } } elseif ($containerStatus === 'exited') { @@ -113,8 +113,53 @@ private function aggregateContainerStatuses($application, $containers) } } + // If all containers are excluded, calculate status from excluded containers + // but mark it with :excluded to indicate monitoring is disabled if ($relevantContainerCount === 0) { - return 'exited:healthy'; + $excludedHasRunning = false; + $excludedHasRestarting = false; + $excludedHasUnhealthy = false; + $excludedHasExited = false; + + foreach ($containers as $container) { + $labels = data_get($container, 'Config.Labels', []); + $serviceName = data_get($labels, 'com.docker.compose.service'); + + // Only process excluded containers + if (! $serviceName || ! $excludedContainers->contains($serviceName)) { + continue; + } + + $containerStatus = data_get($container, 'State.Status'); + $containerHealth = data_get($container, 'State.Health.Status'); + + if ($containerStatus === 'restarting') { + $excludedHasRestarting = true; + $excludedHasUnhealthy = true; + } elseif ($containerStatus === 'running') { + $excludedHasRunning = true; + if ($containerHealth && $containerHealth === 'unhealthy') { + $excludedHasUnhealthy = true; + } + } elseif ($containerStatus === 'exited') { + $excludedHasExited = true; + $excludedHasUnhealthy = true; + } + } + + if ($excludedHasRestarting) { + return 'degraded:excluded'; + } + + if ($excludedHasRunning && $excludedHasExited) { + return 'degraded:excluded'; + } + + if ($excludedHasRunning) { + return 'running:excluded'; + } + + return 'exited:excluded'; } if ($hasRestarting) { diff --git a/app/Models/Service.php b/app/Models/Service.php index 15ee2d1bc..c98c20121 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -184,11 +184,13 @@ public function getStatusAttribute() $complexStatus = null; $complexHealth = null; + $hasNonExcluded = false; foreach ($applications as $application) { if ($application->exclude_from_status) { continue; } + $hasNonExcluded = true; $status = str($application->status)->before('(')->trim(); $health = str($application->status)->between('(', ')')->trim(); if ($complexStatus === 'degraded') { @@ -218,6 +220,7 @@ public function getStatusAttribute() if ($database->exclude_from_status) { continue; } + $hasNonExcluded = true; $status = str($database->status)->before('(')->trim(); $health = str($database->status)->between('(', ')')->trim(); if ($complexStatus === 'degraded') { @@ -244,9 +247,78 @@ public function getStatusAttribute() } } - // If all services are excluded from status checks, return a default exited status - if ($complexStatus === null && $complexHealth === null) { - return 'exited:healthy'; + // If all services are excluded from status checks, calculate status from excluded containers + // but mark it with :excluded to indicate monitoring is disabled + if (! $hasNonExcluded && ($complexStatus === null && $complexHealth === null)) { + $excludedStatus = null; + $excludedHealth = null; + + // Calculate status from excluded containers + foreach ($applications as $application) { + $status = str($application->status)->before('(')->trim(); + $health = str($application->status)->between('(', ')')->trim(); + if ($excludedStatus === 'degraded') { + continue; + } + if ($status->startsWith('running')) { + if ($excludedStatus === 'exited') { + $excludedStatus = 'degraded'; + } else { + $excludedStatus = 'running'; + } + } elseif ($status->startsWith('restarting')) { + $excludedStatus = 'degraded'; + } elseif ($status->startsWith('exited')) { + $excludedStatus = 'exited'; + } + if ($health->value() === 'healthy') { + if ($excludedHealth === 'unhealthy') { + continue; + } + $excludedHealth = 'healthy'; + } else { + $excludedHealth = 'unhealthy'; + } + } + + foreach ($databases as $database) { + $status = str($database->status)->before('(')->trim(); + $health = str($database->status)->between('(', ')')->trim(); + if ($excludedStatus === 'degraded') { + continue; + } + if ($status->startsWith('running')) { + if ($excludedStatus === 'exited') { + $excludedStatus = 'degraded'; + } else { + $excludedStatus = 'running'; + } + } elseif ($status->startsWith('restarting')) { + $excludedStatus = 'degraded'; + } elseif ($status->startsWith('exited')) { + $excludedStatus = 'exited'; + } + if ($health->value() === 'healthy') { + if ($excludedHealth === 'unhealthy') { + continue; + } + $excludedHealth = 'healthy'; + } else { + $excludedHealth = 'unhealthy'; + } + } + + // Return status with :excluded suffix to indicate monitoring is disabled + if ($excludedStatus && $excludedHealth) { + return "{$excludedStatus}:excluded"; + } + + // If no status was calculated at all (no containers exist), return unknown + if ($excludedStatus === null && $excludedHealth === null) { + return 'unknown:excluded'; + } + + return 'exited:excluded'; } return "{$complexStatus}:{$complexHealth}"; diff --git a/resources/views/components/status/services.blade.php b/resources/views/components/status/services.blade.php index 7ea55099f..87db0d64c 100644 --- a/resources/views/components/status/services.blade.php +++ b/resources/views/components/status/services.blade.php @@ -1,13 +1,20 @@ -@if (str($complexStatus)->contains('running')) - -@elseif(str($complexStatus)->contains('starting')) - -@elseif(str($complexStatus)->contains('restarting')) - -@elseif(str($complexStatus)->contains('degraded')) - +@php + $isExcluded = str($complexStatus)->endsWith(':excluded'); + $displayStatus = $isExcluded ? str($complexStatus)->beforeLast(':excluded') : $complexStatus; +@endphp +@if (str($displayStatus)->contains('running')) + +@elseif(str($displayStatus)->contains('starting')) + +@elseif(str($displayStatus)->contains('restarting')) + +@elseif(str($displayStatus)->contains('degraded')) + @else - + +@endif +@if ($isExcluded) + (Monitoring Disabled) @endif @if (!str($complexStatus)->contains('exited') && $showRefreshButton)
@@ -327,13 +327,11 @@ class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:
@if ($loadingServers) -
- - +
+ + @@ -343,8 +341,7 @@ class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg
@elseif (count($availableServers) > 0) @foreach ($availableServers as $index => $server) - @@ -388,10 +384,10 @@ class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:bo
@@ -406,13 +402,11 @@ class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:
@if ($loadingDestinations) -
- - +
+ + @@ -422,25 +416,22 @@ class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg
@elseif (count($availableDestinations) > 0) @foreach ($availableDestinations as $index => $destination) - @@ -462,10 +453,10 @@ class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:bo
@@ -480,13 +471,11 @@ class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:
@if ($loadingProjects) -
- - +
+ + @@ -496,18 +485,15 @@ class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg
@elseif (count($availableProjects) > 0) @foreach ($availableProjects as $index => $project) - @@ -542,10 +528,10 @@ class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:bo
@@ -560,13 +546,11 @@ class="text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:
@if ($loadingEnvironments) -
- - +
+ + @@ -576,18 +560,15 @@ class="flex items-center gap-3 p-3 bg-neutral-50 dark:bg-coolgray-200 rounded-lg
@elseif (count($availableEnvironments) > 0) @foreach ($availableEnvironments as $index => $environment) - @@ -639,8 +620,7 @@ class="search-result-item block px-4 py-3 hover:bg-neutral-50 dark:hover:bg-cool
- + {{ $result['name'] }} +
{{ $result['project'] }} / {{ $result['environment'] }}
@endif @if (!empty($result['description'])) -
+
{{ Str::limit($result['description'], 80) }}
@endif @@ -677,8 +655,8 @@ class="text-sm text-neutral-600 dark:text-neutral-400"> - +
@@ -708,16 +686,15 @@ class="search-result-item w-full text-left block px-4 py-3 hover:bg-yellow-50 da
- + class="h-5 w-5 text-yellow-600 dark:text-yellow-400" fill="none" + viewBox="0 0 24 24" stroke="currentColor"> +
-
+
{{ $item['name'] }}
@if (isset($item['quickcommand'])) @@ -725,8 +702,7 @@ class="font-medium text-neutral-900 dark:text-white truncate"> class="text-xs text-neutral-500 dark:text-neutral-400 shrink-0">{{ $item['quickcommand'] }} @endif
-
+
{{ $item['description'] }}
@@ -734,8 +710,8 @@ class="text-sm text-neutral-600 dark:text-neutral-400 truncate"> - +
@@ -820,8 +796,7 @@ class="search-result-item w-full text-left block px-4 py-3 hover:bg-yellow-50 da class="flex-shrink-0 w-10 h-10 rounded-lg bg-yellow-100 dark:bg-yellow-900/40 flex items-center justify-center"> + fill="none" viewBox="0 0 24 24" stroke="currentColor"> @@ -869,14 +844,6 @@ class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400 self-center"

💡 Tip: Search for service names like "wordpress", "postgres", or "redis"

-
@@ -897,12 +864,10 @@ class="shrink-0 h-5 w-5 text-yellow-500 dark:text-yellow-400 self-center" if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New Project @@ -939,12 +904,10 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New Server @@ -981,12 +944,10 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New Team @@ -1023,12 +984,10 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New S3 Storage @@ -1065,12 +1024,10 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New Private Key @@ -1107,12 +1064,10 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 if (firstInput) firstInput.focus(); }, 200); } - })" - class="fixed top-0 left-0 lg:px-0 px-4 z-99 flex items-center justify-center w-screen h-screen"> -
+
New GitHub App @@ -1139,4 +1094,4 @@ class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5
-
+
\ No newline at end of file From 85b73a8c005fa42d5cc3ffaf654f2036cf7e97c1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:25:25 +0100 Subject: [PATCH 75/95] fix: initialize Collection properties to handle queue deserialization edge cases --- app/Jobs/PushServerUpdateJob.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 9f81155be..b3a0f3590 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -105,6 +105,20 @@ public function __construct(public Server $server, public $data) public function handle() { + // Defensive initialization for Collection properties to handle queue deserialization edge cases + $this->serviceContainerStatuses ??= collect(); + $this->applicationContainerStatuses ??= collect(); + $this->foundApplicationIds ??= collect(); + $this->foundDatabaseUuids ??= collect(); + $this->foundServiceApplicationIds ??= collect(); + $this->foundApplicationPreviewsIds ??= collect(); + $this->foundServiceDatabaseIds ??= collect(); + $this->allApplicationIds ??= collect(); + $this->allDatabaseUuids ??= collect(); + $this->allTcpProxyUuids ??= collect(); + $this->allServiceApplicationIds ??= collect(); + $this->allServiceDatabaseIds ??= collect(); + // TODO: Swarm is not supported yet if (! $this->data) { throw new \Exception('No data provided'); From 2edf2338de07cff658c63a881ef39bb787327b6c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:41:25 +0100 Subject: [PATCH 76/95] fix: enhance getRequiredPort to support map-style environment variables for SERVICE_URL and SERVICE_FQDN --- app/Models/ServiceApplication.php | 21 ++++- tests/Unit/ServiceRequiredPortTest.php | 117 +++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 3 deletions(-) diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index e457dbccd..aef74b402 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -208,10 +208,25 @@ public function getRequiredPort(): ?int // Extract SERVICE_URL and SERVICE_FQDN variables DIRECTLY DECLARED in this service's environment // (not variables that are merely referenced with ${VAR} syntax) $portFound = null; - foreach ($environment as $envVar) { - if (is_string($envVar)) { + foreach ($environment as $key => $value) { + if (is_int($key) && is_string($value)) { + // List-style: "- SERVICE_URL_APP_3000" or "- SERVICE_URL_APP_3000=value" // Extract variable name (before '=' if present) - $envVarName = str($envVar)->before('=')->trim(); + $envVarName = str($value)->before('=')->trim(); + + // Only process direct declarations + if ($envVarName->startsWith('SERVICE_FQDN_') || $envVarName->startsWith('SERVICE_URL_')) { + // Parse to check if it has a port suffix + $parsed = parseServiceEnvironmentVariable($envVarName->value()); + if ($parsed['has_port'] && $parsed['port']) { + // Found a port-specific variable for this service + $portFound = (int) $parsed['port']; + break; + } + } + } elseif (is_string($key)) { + // Map-style: "SERVICE_URL_APP_3000: value" or "SERVICE_FQDN_DB: localhost" + $envVarName = str($key); // Only process direct declarations if ($envVarName->startsWith('SERVICE_FQDN_') || $envVarName->startsWith('SERVICE_URL_')) { diff --git a/tests/Unit/ServiceRequiredPortTest.php b/tests/Unit/ServiceRequiredPortTest.php index 70bf2bca2..2ad345c44 100644 --- a/tests/Unit/ServiceRequiredPortTest.php +++ b/tests/Unit/ServiceRequiredPortTest.php @@ -151,3 +151,120 @@ expect($result)->toBeFalse(); }); + +it('detects port from map-style SERVICE_URL environment variable', function () { + $yaml = <<<'YAML' +services: + trigger: + environment: + SERVICE_URL_TRIGGER_3000: "" + OTHER_VAR: value +YAML; + + $service = Mockery::mock(Service::class)->makePartial(); + $service->docker_compose_raw = $yaml; + $service->shouldReceive('getRequiredPort')->andReturn(null); + + $app = Mockery::mock(ServiceApplication::class)->makePartial(); + $app->name = 'trigger'; + $app->shouldReceive('getAttribute')->with('service')->andReturn($service); + $app->service = $service; + + // Call the actual getRequiredPort method + $result = $app->getRequiredPort(); + + expect($result)->toBe(3000); +}); + +it('detects port from map-style SERVICE_FQDN environment variable', function () { + $yaml = <<<'YAML' +services: + langfuse: + environment: + SERVICE_FQDN_LANGFUSE_3000: localhost + DATABASE_URL: postgres://... +YAML; + + $service = Mockery::mock(Service::class)->makePartial(); + $service->docker_compose_raw = $yaml; + $service->shouldReceive('getRequiredPort')->andReturn(null); + + $app = Mockery::mock(ServiceApplication::class)->makePartial(); + $app->name = 'langfuse'; + $app->shouldReceive('getAttribute')->with('service')->andReturn($service); + $app->service = $service; + + $result = $app->getRequiredPort(); + + expect($result)->toBe(3000); +}); + +it('returns null for map-style environment without port', function () { + $yaml = <<<'YAML' +services: + db: + environment: + SERVICE_FQDN_DB: localhost + SERVICE_URL_DB: http://localhost +YAML; + + $service = Mockery::mock(Service::class)->makePartial(); + $service->docker_compose_raw = $yaml; + $service->shouldReceive('getRequiredPort')->andReturn(null); + + $app = Mockery::mock(ServiceApplication::class)->makePartial(); + $app->name = 'db'; + $app->shouldReceive('getAttribute')->with('service')->andReturn($service); + $app->service = $service; + + $result = $app->getRequiredPort(); + + expect($result)->toBeNull(); +}); + +it('handles list-style environment with port', function () { + $yaml = <<<'YAML' +services: + umami: + environment: + - SERVICE_URL_UMAMI_3000 + - DATABASE_URL=postgres://db/umami +YAML; + + $service = Mockery::mock(Service::class)->makePartial(); + $service->docker_compose_raw = $yaml; + $service->shouldReceive('getRequiredPort')->andReturn(null); + + $app = Mockery::mock(ServiceApplication::class)->makePartial(); + $app->name = 'umami'; + $app->shouldReceive('getAttribute')->with('service')->andReturn($service); + $app->service = $service; + + $result = $app->getRequiredPort(); + + expect($result)->toBe(3000); +}); + +it('prioritizes first port found in environment', function () { + $yaml = <<<'YAML' +services: + multi: + environment: + SERVICE_URL_MULTI_3000: "" + SERVICE_URL_MULTI_8080: "" +YAML; + + $service = Mockery::mock(Service::class)->makePartial(); + $service->docker_compose_raw = $yaml; + $service->shouldReceive('getRequiredPort')->andReturn(null); + + $app = Mockery::mock(ServiceApplication::class)->makePartial(); + $app->name = 'multi'; + $app->shouldReceive('getAttribute')->with('service')->andReturn($service); + $app->service = $service; + + $result = $app->getRequiredPort(); + + // Should return one of the ports (depends on array iteration order) + expect($result)->toBeIn([3000, 8080]); +}); From b62eece93e381cfcdb86f0a3885826b7651edeef Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:48:04 +0100 Subject: [PATCH 77/95] Fix SERVICE_FQDN_DB error by preventing fqdn access on ServiceDatabase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ServiceDatabase doesn't have an fqdn column - only ServiceApplication does. The parser was attempting to read/write fqdn on both types, causing SQL errors when SERVICE_FQDN_* or SERVICE_URL_* variables were used with database services. Now it only persists fqdn to ServiceApplication while still generating the environment variable values for databases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- bootstrap/helpers/parsers.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index 7012e2087..4e0709e49 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1595,7 +1595,11 @@ function serviceParser(Service $resource): Collection $urlFor = $parsed['service_name']; } $port = $parsed['port']; - if (blank($savedService->fqdn)) { + + // Only ServiceApplication has fqdn column, ServiceDatabase does not + $isServiceApplication = $savedService instanceof ServiceApplication; + + if ($isServiceApplication && blank($savedService->fqdn)) { if ($fqdnFor) { $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); } else { @@ -1606,9 +1610,21 @@ function serviceParser(Service $resource): Collection } else { $url = generateUrl($server, "{$savedService->name}-$uuid"); } - } else { + } elseif ($isServiceApplication) { $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); $url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); + } else { + // For ServiceDatabase, generate fqdn/url without saving to the model + if ($fqdnFor) { + $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); + } else { + $fqdn = generateFqdn(server: $server, random: "{$savedService->name}-$uuid", parserVersion: $resource->compose_parsing_version); + } + if ($urlFor) { + $url = generateUrl($server, "$urlFor-$uuid"); + } else { + $url = generateUrl($server, "{$savedService->name}-$uuid"); + } } if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { @@ -1626,7 +1642,8 @@ function serviceParser(Service $resource): Collection if ($url && $port) { $urlWithPort = "$url:$port"; } - if (is_null($savedService->fqdn)) { + // Only save fqdn to ServiceApplication, not ServiceDatabase + if ($isServiceApplication && is_null($savedService->fqdn)) { if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { if ($fqdnFor) { $savedService->fqdn = $fqdnWithPort; From 29135e00baf6b067f2a1098cb265c9640fc45679 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:14:48 +0100 Subject: [PATCH 78/95] feat: enhance prerequisite validation to return detailed results --- app/Actions/Server/ValidatePrerequisites.php | 23 ++++++++-- app/Actions/Server/ValidateServer.php | 7 +-- app/Jobs/ValidateAndInstallServerJob.php | 11 +++-- app/Livewire/Boarding/Index.php | 11 +++-- app/Livewire/Server/ValidateAndInstall.php | 11 +++-- app/Models/Server.php | 7 ++- .../Server/ValidatePrerequisitesTest.php | 46 +++++++++++++++++++ 7 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 tests/Unit/Actions/Server/ValidatePrerequisitesTest.php diff --git a/app/Actions/Server/ValidatePrerequisites.php b/app/Actions/Server/ValidatePrerequisites.php index f74727112..23c1db1d0 100644 --- a/app/Actions/Server/ValidatePrerequisites.php +++ b/app/Actions/Server/ValidatePrerequisites.php @@ -11,17 +11,30 @@ class ValidatePrerequisites public string $jobQueue = 'high'; - public function handle(Server $server): bool + /** + * Validate that required commands are available on the server. + * + * @return array{success: bool, missing: array, found: array} + */ + public function handle(Server $server): array { $requiredCommands = ['git', 'curl', 'jq']; + $missing = []; + $found = []; foreach ($requiredCommands as $cmd) { - $found = instant_remote_process(["command -v {$cmd}"], $server, false); - if (! $found) { - return false; + $result = instant_remote_process(["command -v {$cmd}"], $server, false); + if (! $result) { + $missing[] = $cmd; + } else { + $found[] = $cmd; } } - return true; + return [ + 'success' => empty($missing), + 'missing' => $missing, + 'found' => $found, + ]; } } diff --git a/app/Actions/Server/ValidateServer.php b/app/Actions/Server/ValidateServer.php index a4840b194..0a20deae5 100644 --- a/app/Actions/Server/ValidateServer.php +++ b/app/Actions/Server/ValidateServer.php @@ -45,9 +45,10 @@ public function handle(Server $server) throw new \Exception($this->error); } - $prerequisitesInstalled = $server->validatePrerequisites(); - if (! $prerequisitesInstalled) { - $this->error = 'Prerequisites (git, curl, jq) are not installed. Please install them before continuing or use the validation with installation endpoint.'; + $validationResult = $server->validatePrerequisites(); + if (! $validationResult['success']) { + $missingCommands = implode(', ', $validationResult['missing']); + $this->error = "Prerequisites ({$missingCommands}) are not installed. Please install them before continuing or use the validation with installation endpoint."; $server->update([ 'validation_logs' => $this->error, ]); diff --git a/app/Jobs/ValidateAndInstallServerJob.php b/app/Jobs/ValidateAndInstallServerJob.php index a6dcd62f1..ff5c2e4f5 100644 --- a/app/Jobs/ValidateAndInstallServerJob.php +++ b/app/Jobs/ValidateAndInstallServerJob.php @@ -73,10 +73,11 @@ public function handle(): void } // Check and install prerequisites - $prerequisitesInstalled = $this->server->validatePrerequisites(); - if (! $prerequisitesInstalled) { + $validationResult = $this->server->validatePrerequisites(); + if (! $validationResult['success']) { if ($this->numberOfTries >= $this->maxTries) { - $errorMessage = 'Prerequisites (git, curl, jq) could not be installed after '.$this->maxTries.' attempts. Please install them manually before continuing.'; + $missingCommands = implode(', ', $validationResult['missing']); + $errorMessage = "Prerequisites ({$missingCommands}) could not be installed after {$this->maxTries} attempts. Please install them manually before continuing."; $this->server->update([ 'validation_logs' => $errorMessage, 'is_validating' => false, @@ -84,6 +85,8 @@ public function handle(): void Log::error('ValidateAndInstallServer: Prerequisites installation failed after max tries', [ 'server_id' => $this->server->id, 'attempts' => $this->numberOfTries, + 'missing_commands' => $validationResult['missing'], + 'found_commands' => $validationResult['found'], ]); return; @@ -92,6 +95,8 @@ public function handle(): void Log::info('ValidateAndInstallServer: Installing prerequisites', [ 'server_id' => $this->server->id, 'attempt' => $this->numberOfTries + 1, + 'missing_commands' => $validationResult['missing'], + 'found_commands' => $validationResult['found'], ]); // Install prerequisites diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index dfddd7f68..25a2fd694 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -322,13 +322,14 @@ public function validateServer() try { // Check prerequisites - $prerequisitesInstalled = $this->createdServer->validatePrerequisites(); - if (! $prerequisitesInstalled) { + $validationResult = $this->createdServer->validatePrerequisites(); + if (! $validationResult['success']) { $this->createdServer->installPrerequisites(); // Recheck after installation - $prerequisitesInstalled = $this->createdServer->validatePrerequisites(); - if (! $prerequisitesInstalled) { - throw new \Exception('Prerequisites (git, curl, jq) could not be installed. Please install them manually.'); + $validationResult = $this->createdServer->validatePrerequisites(); + if (! $validationResult['success']) { + $missingCommands = implode(', ', $validationResult['missing']); + throw new \Exception("Prerequisites ({$missingCommands}) could not be installed. Please install them manually."); } } } catch (\Throwable $e) { diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index 687eadd48..d2e45ded2 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -115,11 +115,13 @@ public function validateOS() public function validatePrerequisites() { - $this->prerequisites_installed = $this->server->validatePrerequisites(); - if (! $this->prerequisites_installed) { + $validationResult = $this->server->validatePrerequisites(); + $this->prerequisites_installed = $validationResult['success']; + if (! $validationResult['success']) { if ($this->install) { if ($this->number_of_tries == $this->max_tries) { - $this->error = 'Prerequisites (git, curl, jq) could not be installed. Please install them manually before continuing.'; + $missingCommands = implode(', ', $validationResult['missing']); + $this->error = "Prerequisites ({$missingCommands}) could not be installed. Please install them manually before continuing."; $this->server->update([ 'validation_logs' => $this->error, ]); @@ -136,7 +138,8 @@ public function validatePrerequisites() return; } } else { - $this->error = 'Prerequisites (git, curl, jq) are not installed. Please install them before continuing.'; + $missingCommands = implode(', ', $validationResult['missing']); + $this->error = "Prerequisites ({$missingCommands}) are not installed. Please install them before continuing."; $this->server->update([ 'validation_logs' => $this->error, ]); diff --git a/app/Models/Server.php b/app/Models/Server.php index 9210e801b..8b153c8ac 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1186,7 +1186,12 @@ public function installDocker() return InstallDocker::run($this); } - public function validatePrerequisites(): bool + /** + * Validate that required commands are available on the server. + * + * @return array{success: bool, missing: array, found: array} + */ + public function validatePrerequisites(): array { return ValidatePrerequisites::run($this); } diff --git a/tests/Unit/Actions/Server/ValidatePrerequisitesTest.php b/tests/Unit/Actions/Server/ValidatePrerequisitesTest.php new file mode 100644 index 000000000..8db6815d6 --- /dev/null +++ b/tests/Unit/Actions/Server/ValidatePrerequisitesTest.php @@ -0,0 +1,46 @@ +toBeTrue() + ->and('ValidatePrerequisites should return array with keys: '.implode(', ', $expectedKeys)) + ->toBeString(); +}); + +it('validates required commands list', function () { + // Verify the action checks for the correct prerequisites + $requiredCommands = ['git', 'curl', 'jq']; + + expect($requiredCommands)->toHaveCount(3) + ->and($requiredCommands)->toContain('git') + ->and($requiredCommands)->toContain('curl') + ->and($requiredCommands)->toContain('jq'); +}); + +it('return structure has correct types', function () { + // Verify the expected return structure types + $expectedStructure = [ + 'success' => 'boolean', + 'missing' => 'array', + 'found' => 'array', + ]; + + expect($expectedStructure['success'])->toBe('boolean') + ->and($expectedStructure['missing'])->toBe('array') + ->and($expectedStructure['found'])->toBe('array'); +}); From bc39c2caa83b511b5fead29e2b40827e2471a8fb Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:29:06 +0100 Subject: [PATCH 79/95] fix: eliminate layout shift on input border indicator using box-shadow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace border-based left indicator with inset box-shadow to prevent unwanted layout shifts when focusing or marking fields as dirty. The solution reserves 4px space with transparent shadow in default state and transitions to colored shadow on focus/dirty without affecting the box model. Update all form components (input, textarea, select, datalist) for consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- resources/css/utilities.css | 60 +++++++++++++++++-- .../views/components/forms/datalist.blade.php | 16 +++-- .../views/components/forms/input.blade.php | 4 +- .../views/components/forms/select.blade.php | 2 +- .../views/components/forms/textarea.blade.php | 10 ++-- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/resources/css/utilities.css b/resources/css/utilities.css index 5d8a6bfa1..2899ea1e5 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -32,7 +32,20 @@ @utility apexcharts-tooltip-custom-title { } @utility input-sticky { - @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning; + @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 dark:bg-coolgray-100 dark:text-white disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40 focus-visible:outline-none; + box-shadow: inset 4px 0 0 transparent, inset 0 0 0 1px #e5e5e5; + + &:where(.dark, .dark *) { + box-shadow: inset 4px 0 0 transparent, inset 0 0 0 1px #242424; + } + + &:focus-visible { + box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 1px #e5e5e5; + } + + &:where(.dark, .dark *):focus-visible { + box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 1px #242424; + } } @utility input-sticky-active { @@ -46,20 +59,49 @@ @utility input-focus { /* input, select before */ @utility input-select { - @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-2 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40 dark:disabled:ring-transparent; + @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 dark:bg-coolgray-100 dark:text-white disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40; + box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #e5e5e5; + + &:where(.dark, .dark *) { + box-shadow: inset 4px 0 0 transparent, inset 0 0 0 2px #242424; + } + + &:disabled { + box-shadow: none; + } + + &:where(.dark, .dark *):disabled { + box-shadow: none; + } } /* Readonly */ @utility input { - @apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200; + @apply dark:read-only:text-neutral-500 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200; @apply input-select; - @apply focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning; + @apply focus-visible:outline-none; + + &:focus-visible { + box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5; + } + + &:where(.dark, .dark *):focus-visible { + box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424; + } + + &:read-only { + box-shadow: none; + } + + &:where(.dark, .dark *):read-only { + box-shadow: none; + } } @utility select { @apply w-full; @apply input-select; - @apply focus-visible:outline-none focus-visible:border-l-4 focus-visible:border-l-coollabs dark:focus-visible:border-l-warning; + @apply focus-visible:outline-none; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'/%3e%3c/svg%3e"); background-position: right 0.5rem center; background-repeat: no-repeat; @@ -69,6 +111,14 @@ @utility select { &:where(.dark, .dark *) { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23ffffff'%3e%3cpath stroke-linecap='round' stroke-linejoin='round' d='M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9'/%3e%3c/svg%3e"); } + + &:focus-visible { + box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5; + } + + &:where(.dark, .dark *):focus-visible { + box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424; + } } @utility button { diff --git a/resources/views/components/forms/datalist.blade.php b/resources/views/components/forms/datalist.blade.php index 79a14d16f..1d9a3b263 100644 --- a/resources/views/components/forms/datalist.blade.php +++ b/resources/views/components/forms/datalist.blade.php @@ -97,12 +97,14 @@ }" @click.outside="open = false" class="relative"> {{-- Unified Input Container with Tags Inside --}} -
+ wire:dirty.class="[box-shadow:inset_4px_0_0_#6b16ed,inset_0_0_0_2px_#e5e5e5] dark:[box-shadow:inset_4px_0_0_#fcd452,inset_0_0_0_2px_#242424]"> {{-- Selected Tags Inside Input --}}