From 850c486a6eff1e58b01bde1bb3cac3bbf42c8fd7 Mon Sep 17 00:00:00 2001 From: David Londono Date: Thu, 28 Aug 2025 16:10:33 -0500 Subject: [PATCH 001/432] add appflowy --- public/svgs/appflowy.svg | 12 ++ templates/compose/appflowy.yaml | 217 ++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 public/svgs/appflowy.svg create mode 100644 templates/compose/appflowy.yaml diff --git a/public/svgs/appflowy.svg b/public/svgs/appflowy.svg new file mode 100644 index 000000000..7853ed36e --- /dev/null +++ b/public/svgs/appflowy.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/templates/compose/appflowy.yaml b/templates/compose/appflowy.yaml new file mode 100644 index 000000000..805134b71 --- /dev/null +++ b/templates/compose/appflowy.yaml @@ -0,0 +1,217 @@ +# documentation: https://docs.appflowy.io/docs/documentation/appflowy-cloud +# slogan: AppFlowy is the AI collaborative workspace where you achieve more without losing control of your data. +# tags: appflowy,notion,open-source,productivity,notes,ai,self-hosted +# logo: svgs/appflowy.svg +# category: productivity +# port: 80 + +services: + appflowy_web: + environment: + - SERVICE_FQDN_APPFLOWYWEB_80 + - APPFLOWY_BASE_URL=${SERVICE_FQDN_APPFLOWYCLOUD_8000} + - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_FQDN_GOTRUE_9999} + - APPFLOWY_WS_BASE_URL=${SERVICE_FQDN_APPFLOWYCLOUD_8000}/ws/v2 + image: appflowyinc/appflowy_web:latest + depends_on: + - appflowy_cloud + + appflowy_cloud: + image: appflowyinc/appflowy_cloud:latest + labels: + - "traefik.http.middlewares.appflowy-cors.headers.customRequestHeaders.X-Request-Id={{.RequestID}}" + - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowMethods=GET,POST,PUT,DELETE,PATCH,OPTIONS" + - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowHeaders=Content-Type,Authorization,Accept,Client-Version,Device-Id" + - "traefik.http.middlewares.appflowy-cors.headers.accessControlMaxAge=3600" + - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowOriginList=*" + - "traefik.http.middlewares.appflowy-cors.headers.addvaryheader=true" + environment: + - SERVICE_FQDN_APPFLOWYCLOUD_8000 + - RUST_LOG=${RUST_LOG:-info} + - APPFLOWY_ENVIRONMENT=production + - APPFLOWY_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy} + - APPFLOWY_REDIS_URI=redis://redis:6379 + - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} + - APPFLOWY_GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200} + - APPFLOWY_GOTRUE_BASE_URL=http://gotrue:9999 + - APPFLOWY_S3_CREATE_BUCKET=${APPFLOWY_S3_CREATE_BUCKET:-true} + - APPFLOWY_S3_USE_MINIO=true + - APPFLOWY_S3_MINIO_URL=http://minio:9000 + - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO} + - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO} + - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy} + - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1} + - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST:-smtp.gmail.com} + - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT:-587} + - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME:-notify@appflowy.io} + - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL:-notify@appflowy.io} + - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD:-email_sender_password} + - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND:-none} + - APPFLOWY_ACCESS_CONTROL=true + - APPFLOWY_DATABASE_MAX_CONNECTIONS=40 + - AI_SERVER_HOST=ai + - AI_SERVER_PORT=5001 + - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY:-} + - APPFLOWY_WEB_URL=${SERVICE_FQDN_APPFLOWYWEB_80} + depends_on: + gotrue: + condition: service_healthy + postgres: + condition: service_healthy + redis: + condition: service_started + + gotrue: + image: appflowyinc/gotrue:latest + environment: + - SERVICE_FQDN_GOTRUE_9999 + - GOTRUE_API_HOST=0.0.0.0 + - GOTRUE_API_PORT=9999 + - PORT=9999 + - GOTRUE_ADMIN_EMAIL=${GOTRUE_ADMIN_EMAIL:-admin@example.com} + - GOTRUE_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN} + - GOTRUE_DISABLE_SIGNUP=false + - GOTRUE_SITE_URL=appflowy-flutter:// + - GOTRUE_URI_ALLOW_LIST=** + - GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} + - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200} + - GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin + - GOTRUE_DB_DRIVER=postgres + - API_EXTERNAL_URL=${SERVICE_FQDN_GOTRUE_9999} + - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}?search_path=auth + - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST} + - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT:-587} + - GOTRUE_SMTP_USER=${GOTRUE_SMTP_USER} + - GOTRUE_SMTP_PASS=${GOTRUE_SMTP_PASS} + - GOTRUE_MAILER_URLPATHS_CONFIRMATION=/verify + - GOTRUE_MAILER_URLPATHS_INVITE=/verify + - GOTRUE_MAILER_URLPATHS_RECOVERY=/verify + - GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/verify + - GOTRUE_SMTP_ADMIN_EMAIL=${GOTRUE_SMTP_ADMIN_EMAIL} + - GOTRUE_SMTP_MAX_FREQUENCY=1ns + - GOTRUE_RATE_LIMIT_EMAIL_SENT=100 + - GOTRUE_MAILER_AUTOCONFIRM=true + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: "curl --fail http://127.0.0.1:9999/health || exit 1" + interval: 5s + timeout: 5s + retries: 12 + + admin_frontend: + image: appflowyinc/admin_frontend:latest + environment: + - SERVICE_FQDN_ADMINFRONTEND_3000 + - RUST_LOG=info + - ADMIN_FRONTEND_REDIS_URL=redis://redis:6379 + - ADMIN_FRONTEND_GOTRUE_URL=http://gotrue:9999 + - ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=http://appflowy_cloud:8000 + - ADMIN_FRONTEND_PATH_PREFIX=/console + - ADMIN_FRONTEND_HOST=0.0.0.0 + - ADMIN_FRONTEND_PORT=3000 + - ADMIN_FRONTEND_OAUTH_CLIENT_ID=appflowy_cloud + - ADMIN_FRONTEND_OAUTH_CLIENT_SECRET=${SERVICE_PASSWORD_ADMINOAUTH} + - ADMIN_FRONTEND_OAUTH_ALLOWABLE_REDIRECT_URIS=${SERVICE_FQDN_ADMINFRONTEND_3000} + depends_on: + gotrue: + condition: service_healthy + appflowy_cloud: + condition: service_started + + ai: + image: appflowyinc/appflowy_ai:latest + environment: + - OPENAI_API_KEY=${AI_OPENAI_API_KEY:-} + - AI_SERVER_PORT=5001 + - DEFAULT_AI_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account + - DEFAULT_AI_COMPLETION_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account + - AI_APPFLOWY_HOST=${SERVICE_FQDN_APPFLOWYWEB_80} + - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} + + - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY} + - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT} + - AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION} + - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO} + - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO} + - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy} + - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1} + - AI_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy} + - AI_REDIS_URL=redis://redis:6379 + - AI_USE_MINIO=true + - AI_MINIO_URL=http://minio:9000 + depends_on: + postgres: + condition: service_healthy + + appflowy_worker: + image: appflowyinc/appflowy_worker:latest + environment: + - RUST_LOG=info + - APPFLOWY_ENVIRONMENT=production + - APPFLOWY_WORKER_ENVIRONMENT=production + - APPFLOWY_WORKER_REDIS_URL=redis://redis:6379 + - APPFLOWY_WORKER_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy} + - APPFLOWY_WORKER_DATABASE_NAME=${POSTGRES_DB:-appflowy} + - APPFLOWY_WORKER_IMPORT_TICK_INTERVAL=30 + - APPFLOWY_S3_USE_MINIO=true + - APPFLOWY_S3_MINIO_URL=http://minio:9000 + - APPFLOWY_S3_ACCESS_KEY=${SERVICE_USER_MINIO} + - APPFLOWY_S3_SECRET_KEY=${SERVICE_PASSWORD_MINIO} + - APPFLOWY_S3_BUCKET=${APPFLOWY_S3_BUCKET:-appflowy} + - APPFLOWY_S3_REGION=${APPFLOWY_S3_REGION:-us-east-1} + - APPFLOWY_MAILER_SMTP_HOST=${APPFLOWY_MAILER_SMTP_HOST:-smtp.gmail.com} + - APPFLOWY_MAILER_SMTP_PORT=${APPFLOWY_MAILER_SMTP_PORT:-587} + - APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME:-notify@appflowy.io} + - APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL:-notify@appflowy.io} + - APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD:-email_sender_password} + - APPFLOWY_MAILER_SMTP_TLS_KIND=${APPFLOWY_MAILER_SMTP_TLS_KIND:-none} + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_started + + minio: + image: minio/minio + environment: + - MINIO_ROOT_USER=${SERVICE_USER_MINIO} + - MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO} + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 5s + timeout: 20s + retries: 10 + volumes: + # Coolify will manage this named volume for persistent object storage. + - minio_data:/data + + postgres: + image: pgvector/pgvector:pg16 + environment: + - POSTGRES_USER=${SERVICE_USER_POSTGRES} + - POSTGRES_DB=${POSTGRES_DB:-appflowy} + - POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES} + - POSTGRES_HOST=postgres + volumes: + # Coolify will manage this named volume for persistent database storage. + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD", "pg_isready", "-U", "${SERVICE_USER_POSTGRES}", "-d", "${POSTGRES_DB:-appflowy}" ] + interval: 5s + timeout: 5s + retries: 12 + + redis: + image: redis + volumes: + - "redis_data:/data" + healthcheck: + test: + - CMD-SHELL + - "redis-cli -h localhost -p 6379 ping" + interval: 5s + timeout: 5s + retries: 3 From f7a311149ccf1f02c3b888386c9a3ab225317e6f Mon Sep 17 00:00:00 2001 From: David Londono Date: Wed, 3 Sep 2025 09:08:10 -0500 Subject: [PATCH 002/432] reverse proxy for mobile --- templates/compose/appflowy.yaml | 153 ++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 17 deletions(-) diff --git a/templates/compose/appflowy.yaml b/templates/compose/appflowy.yaml index 805134b71..6f9dfc579 100644 --- a/templates/compose/appflowy.yaml +++ b/templates/compose/appflowy.yaml @@ -6,12 +6,137 @@ # port: 80 services: + nginx: + image: nginx:latest + environment: + - SERVICE_URL_BACKEND + volumes: + - type: bind + source: ./nginx/nginx.conf + target: /etc/nginx/nginx.conf + read_only: true + content: | + # Please do not directly edit this file. + + error_log /var/log/nginx/error.log notice; + pid /var/run/nginx.pid; + + + events { + worker_connections 1024; + } + + + http { + resolver 127.0.0.11 valid=10s; + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80; + client_max_body_size 10M; + underscores_in_headers on; + + # Backend services + set $appflowy_cloud_backend "http://appflowy_cloud:8000"; + set $gotrue_backend "http://gotrue:9999"; + # GoTrue Authentication Service + location /gotrue/ { + proxy_pass $gotrue_backend; + rewrite ^/gotrue(/.*)$ $1 break; + # Allow headers like redirect_to to be handed over to the gotrue + # for correct redirecting + proxy_set_header Host $http_host; + proxy_pass_request_headers on; + } + + # WebSocket endpoint - handle both /ws and /ws/v2 + location ~ ^/ws(/.*)?$ { + proxy_pass $appflowy_cloud_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 86400s; + } + + # API endpoints + location /api { + proxy_pass $appflowy_cloud_backend; + + # Headers + proxy_set_header X-Request-Id $request_id; + proxy_set_header Host $http_host; + + # Special handling for publish endpoint + location ~* ^/api/workspace/([a-zA-Z0-9_-]+)/publish$ { + proxy_pass $appflowy_cloud_backend; + proxy_request_buffering off; + client_max_body_size 256M; + } + + # Chat endpoint (streaming) + location /api/chat { + proxy_pass $appflowy_cloud_backend; + + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding on; + proxy_buffering off; + proxy_cache off; + + proxy_read_timeout 600s; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + } + + # Import endpoint + location /api/import { + proxy_pass $appflowy_cloud_backend; + + proxy_set_header X-Request-Id $request_id; + proxy_set_header Host $http_host; + + proxy_read_timeout 600s; + proxy_connect_timeout 600s; + proxy_send_timeout 600s; + + proxy_request_buffering off; + proxy_buffering off; + proxy_cache off; + client_max_body_size 2G; + } + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } + } + depends_on: + - appflowy_cloud + - gotrue + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + appflowy_web: environment: - - SERVICE_FQDN_APPFLOWYWEB_80 - - APPFLOWY_BASE_URL=${SERVICE_FQDN_APPFLOWYCLOUD_8000} - - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_FQDN_GOTRUE_9999} - - APPFLOWY_WS_BASE_URL=${SERVICE_FQDN_APPFLOWYCLOUD_8000}/ws/v2 + - SERVICE_URL_APPFLOWYWEB + - APPFLOWY_BASE_URL=${SERVICE_URL_CLOUD} + - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_GOTRUE} + - APPFLOWY_WS_BASE_URL=${SERVICE_URL_CLOUD}/ws/v2 image: appflowyinc/appflowy_web:latest depends_on: - appflowy_cloud @@ -26,8 +151,9 @@ services: - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowOriginList=*" - "traefik.http.middlewares.appflowy-cors.headers.addvaryheader=true" environment: - - SERVICE_FQDN_APPFLOWYCLOUD_8000 + - SERVICE_URL_CLOUD_8000 - RUST_LOG=${RUST_LOG:-info} + - APPFLOWY_BASE_URL=${SERVICE_URL_CLOUD} - APPFLOWY_ENVIRONMENT=production - APPFLOWY_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy} - APPFLOWY_REDIS_URI=redis://redis:6379 @@ -52,7 +178,7 @@ services: - AI_SERVER_HOST=ai - AI_SERVER_PORT=5001 - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY:-} - - APPFLOWY_WEB_URL=${SERVICE_FQDN_APPFLOWYWEB_80} + - APPFLOWY_WEB_URL=${SERVICE_URL_APPFLOWYWEB} depends_on: gotrue: condition: service_healthy @@ -64,7 +190,7 @@ services: gotrue: image: appflowyinc/gotrue:latest environment: - - SERVICE_FQDN_GOTRUE_9999 + - SERVICE_URL_GOTRUE_9999 - GOTRUE_API_HOST=0.0.0.0 - GOTRUE_API_PORT=9999 - PORT=9999 @@ -75,9 +201,8 @@ services: - GOTRUE_URI_ALLOW_LIST=** - GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200} - - GOTRUE_JWT_ADMIN_GROUP_NAME=supabase_admin - GOTRUE_DB_DRIVER=postgres - - API_EXTERNAL_URL=${SERVICE_FQDN_GOTRUE_9999} + - API_EXTERNAL_URL=${SERVICE_URL_GOTRUE} - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}?search_path=auth - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST} - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT:-587} @@ -103,17 +228,11 @@ services: admin_frontend: image: appflowyinc/admin_frontend:latest environment: - - SERVICE_FQDN_ADMINFRONTEND_3000 + - SERVICE_URL_ADMINFRONTEND_3000 - RUST_LOG=info - ADMIN_FRONTEND_REDIS_URL=redis://redis:6379 - ADMIN_FRONTEND_GOTRUE_URL=http://gotrue:9999 - ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=http://appflowy_cloud:8000 - - ADMIN_FRONTEND_PATH_PREFIX=/console - - ADMIN_FRONTEND_HOST=0.0.0.0 - - ADMIN_FRONTEND_PORT=3000 - - ADMIN_FRONTEND_OAUTH_CLIENT_ID=appflowy_cloud - - ADMIN_FRONTEND_OAUTH_CLIENT_SECRET=${SERVICE_PASSWORD_ADMINOAUTH} - - ADMIN_FRONTEND_OAUTH_ALLOWABLE_REDIRECT_URIS=${SERVICE_FQDN_ADMINFRONTEND_3000} depends_on: gotrue: condition: service_healthy @@ -127,7 +246,7 @@ services: - AI_SERVER_PORT=5001 - DEFAULT_AI_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account - DEFAULT_AI_COMPLETION_MODEL=gpt-4.1-mini # Make sure the model is available in your OpenAI account - - AI_APPFLOWY_HOST=${SERVICE_FQDN_APPFLOWYWEB_80} + - AI_APPFLOWY_HOST=${SERVICE_URL_APPFLOWYWEB} - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY} From cf4270140cfe859c406479302c453a9afe02a4a2 Mon Sep 17 00:00:00 2001 From: David Londono Date: Thu, 16 Oct 2025 11:26:29 -0500 Subject: [PATCH 003/432] fix: update cors and versions --- templates/compose/appflowy.yaml | 95 ++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/templates/compose/appflowy.yaml b/templates/compose/appflowy.yaml index 6f9dfc579..93ac8fe09 100644 --- a/templates/compose/appflowy.yaml +++ b/templates/compose/appflowy.yaml @@ -7,9 +7,9 @@ services: nginx: - image: nginx:latest + image: nginx:1.29.2 environment: - - SERVICE_URL_BACKEND + - SERVICE_URL_ADMIN volumes: - type: bind source: ./nginx/nginx.conf @@ -42,10 +42,13 @@ services: # Backend services set $appflowy_cloud_backend "http://appflowy_cloud:8000"; set $gotrue_backend "http://gotrue:9999"; + set $admin_frontend_backend "http://admin_frontend:3000"; # GoTrue Authentication Service location /gotrue/ { proxy_pass $gotrue_backend; + rewrite ^/gotrue(/.*)$ $1 break; + # Allow headers like redirect_to to be handed over to the gotrue # for correct redirecting proxy_set_header Host $http_host; @@ -53,8 +56,9 @@ services: } # WebSocket endpoint - handle both /ws and /ws/v2 - location ~ ^/ws(/.*)?$ { + location /ws { proxy_pass $appflowy_cloud_backend; + proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -68,8 +72,6 @@ services: # API endpoints location /api { proxy_pass $appflowy_cloud_backend; - - # Headers proxy_set_header X-Request-Id $request_id; proxy_set_header Host $http_host; @@ -113,6 +115,33 @@ services: } } + # Admin Frontend + # Optional Module, comment this section if you did not deploy admin_frontend in docker-compose.yml + location /console { + proxy_pass $admin_frontend_backend; + + proxy_set_header X-Scheme $scheme; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_set_header Connection ""; + + proxy_buffering off; + proxy_cache off; + + proxy_read_timeout 60s; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + } + + # Redirect root path to /console + location = / { + return 301 /console; + } + # Health check endpoint location /health { access_log off; @@ -124,6 +153,7 @@ services: depends_on: - appflowy_cloud - gotrue + - admin_frontend healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s @@ -134,31 +164,22 @@ services: appflowy_web: environment: - SERVICE_URL_APPFLOWYWEB - - APPFLOWY_BASE_URL=${SERVICE_URL_CLOUD} - - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_GOTRUE} - - APPFLOWY_WS_BASE_URL=${SERVICE_URL_CLOUD}/ws/v2 - image: appflowyinc/appflowy_web:latest + - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN} + - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_ADMIN}/gotrue + - APPFLOWY_WS_BASE_URL=${SERVICE_URL_ADMIN}/ws/v2 + image: appflowyinc/appflowy_web:0.9.132 depends_on: - appflowy_cloud appflowy_cloud: - image: appflowyinc/appflowy_cloud:latest - labels: - - "traefik.http.middlewares.appflowy-cors.headers.customRequestHeaders.X-Request-Id={{.RequestID}}" - - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowMethods=GET,POST,PUT,DELETE,PATCH,OPTIONS" - - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowHeaders=Content-Type,Authorization,Accept,Client-Version,Device-Id" - - "traefik.http.middlewares.appflowy-cors.headers.accessControlMaxAge=3600" - - "traefik.http.middlewares.appflowy-cors.headers.accessControlAllowOriginList=*" - - "traefik.http.middlewares.appflowy-cors.headers.addvaryheader=true" + image: appflowyinc/appflowy_cloud:0.9.149 environment: - - SERVICE_URL_CLOUD_8000 - RUST_LOG=${RUST_LOG:-info} - - APPFLOWY_BASE_URL=${SERVICE_URL_CLOUD} + - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN} - APPFLOWY_ENVIRONMENT=production - APPFLOWY_DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy} - APPFLOWY_REDIS_URI=redis://redis:6379 - APPFLOWY_GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} - - APPFLOWY_GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200} - APPFLOWY_GOTRUE_BASE_URL=http://gotrue:9999 - APPFLOWY_S3_CREATE_BUCKET=${APPFLOWY_S3_CREATE_BUCKET:-true} - APPFLOWY_S3_USE_MINIO=true @@ -179,6 +200,12 @@ services: - AI_SERVER_PORT=5001 - AI_OPENAI_API_KEY=${AI_OPENAI_API_KEY:-} - APPFLOWY_WEB_URL=${SERVICE_URL_APPFLOWYWEB} + healthcheck: + test: "curl --fail http://127.0.0.1:8000/api/health || exit 1" + interval: 5s + timeout: 5s + retries: 12 + depends_on: gotrue: condition: service_healthy @@ -188,9 +215,8 @@ services: condition: service_started gotrue: - image: appflowyinc/gotrue:latest + image: appflowyinc/gotrue:0.9.149 environment: - - SERVICE_URL_GOTRUE_9999 - GOTRUE_API_HOST=0.0.0.0 - GOTRUE_API_PORT=9999 - PORT=9999 @@ -202,7 +228,7 @@ services: - GOTRUE_JWT_SECRET=${SERVICE_PASSWORD_JWT} - GOTRUE_JWT_EXP=${GOTRUE_JWT_EXP:-7200} - GOTRUE_DB_DRIVER=postgres - - API_EXTERNAL_URL=${SERVICE_URL_GOTRUE} + - API_EXTERNAL_URL=${SERVICE_URL_ADMIN}/gotrue - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-appflowy}?search_path=auth - GOTRUE_SMTP_HOST=${GOTRUE_SMTP_HOST} - GOTRUE_SMTP_PORT=${GOTRUE_SMTP_PORT:-587} @@ -226,13 +252,10 @@ services: retries: 12 admin_frontend: - image: appflowyinc/admin_frontend:latest + image: appflowyinc/admin_frontend:0.9.149 environment: - - SERVICE_URL_ADMINFRONTEND_3000 - - RUST_LOG=info - - ADMIN_FRONTEND_REDIS_URL=redis://redis:6379 - - ADMIN_FRONTEND_GOTRUE_URL=http://gotrue:9999 - - ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=http://appflowy_cloud:8000 + - APPFLOWY_GOTRUE_BASE_URL=${SERVICE_URL_ADMIN}/gotrue + - APPFLOWY_BASE_URL=${SERVICE_URL_ADMIN} depends_on: gotrue: condition: service_healthy @@ -240,7 +263,7 @@ services: condition: service_started ai: - image: appflowyinc/appflowy_ai:latest + image: appflowyinc/appflowy_ai:0.9.149 environment: - OPENAI_API_KEY=${AI_OPENAI_API_KEY:-} - AI_SERVER_PORT=5001 @@ -265,7 +288,7 @@ services: condition: service_healthy appflowy_worker: - image: appflowyinc/appflowy_worker:latest + image: appflowyinc/appflowy_worker:0.9.149 environment: - RUST_LOG=info - APPFLOWY_ENVIRONMENT=production @@ -293,16 +316,16 @@ services: condition: service_started minio: - image: minio/minio + image: minio/minio:RELEASE.2025-09-07T16-13-09Z environment: - MINIO_ROOT_USER=${SERVICE_USER_MINIO} - MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO} command: server /data --console-address ":9001" healthcheck: - test: ["CMD", "mc", "ready", "local"] - interval: 5s + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s timeout: 20s - retries: 10 + retries: 3 volumes: # Coolify will manage this named volume for persistent object storage. - minio_data:/data @@ -324,7 +347,7 @@ services: retries: 12 redis: - image: redis + image: redis:8.2.2-alpine3.22 volumes: - "redis_data:/data" healthcheck: From ad148eca60bdc3d454f0178bae66ff227d3fb01a Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:04:19 +0100 Subject: [PATCH 004/432] Add automatic shared dependencies for worktrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setup Conductor to automatically share node_modules and vendor directories across all git worktrees to save disk space and speed up development. Changes: - Updated conductor-setup.sh to create symlinks to shared dependencies - Added documentation to CLAUDE.md explaining the system - Dependencies now stored in .shared-deps/ in main repository - All worktrees use the same dependency versions automatically Benefits: - Saves hundreds of MBs to GBs of disk space - No need to run npm install/composer install for each worktree - Consistent dependency versions across all worktrees Note: Add .shared-deps/ to .gitignore in the main repository 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 38 ++++++++++++++++++++++++++++++++++++++ scripts/conductor-setup.sh | 30 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index b7c496e42..e8c040402 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,44 @@ This file provides guidance to **Claude Code** (claude.ai/code) when working wit Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization. +## Git Worktree Shared Dependencies + +This repository uses git worktrees for parallel development with **automatic shared dependency setup** via Conductor. + +### How It Works + +The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically: +1. Creates a shared `.shared-deps/` directory in the main repository +2. Creates symlinks from `node_modules` and `vendor` to the shared location +3. This happens automatically when Conductor creates a new worktree + +### Benefits + +- **Save disk space**: Only one copy of dependencies across all worktrees +- **Faster setup**: No need to run `npm install` or `composer install` for each worktree +- **Consistent versions**: All worktrees use the same dependency versions +- **Auto-configured**: Handled by Conductor's setup script + +### Manual Setup (If Needed) + +If you need to set up symlinks manually or for non-Conductor worktrees: + +```bash +# From the worktree directory +SHARED_DEPS="../../.shared-deps" +mkdir -p "$SHARED_DEPS/node_modules" "$SHARED_DEPS/vendor" +rm -rf node_modules vendor +ln -sf "$SHARED_DEPS/node_modules" node_modules +ln -sf "$SHARED_DEPS/vendor" vendor +``` + +### Important Notes + +- Dependencies are shared at `$CONDUCTOR_ROOT_PATH/.shared-deps/` +- Run `npm install` or `composer install` from any worktree to update all +- Ensure `.shared-deps/` is in `.gitignore` (should already be there) +- If different branches need different dependency versions, this won't work - remove symlinks and use separate directories + ## Development Commands ### Frontend Development diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index 7712f88be..963c8e808 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -1 +1,29 @@ -cp $CONDUCTOR_ROOT_PATH/.env .env \ No newline at end of file +#!/bin/bash +set -e + +# Copy .env file +cp $CONDUCTOR_ROOT_PATH/.env .env + +# Setup shared dependencies via symlinks +echo "Setting up shared node_modules and vendor directories..." + +# Create shared-deps directory in main repository if it doesn't exist +SHARED_DEPS="$CONDUCTOR_ROOT_PATH/.shared-deps" +mkdir -p "$SHARED_DEPS/node_modules" +mkdir -p "$SHARED_DEPS/vendor" + +# Remove existing directories if they exist and are not symlinks +[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules +[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor + +# Calculate relative path from worktree to shared deps +WORKTREE_PATH=$(pwd) +RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$SHARED_DEPS', '$WORKTREE_PATH'))") + +# Create symlinks +ln -sf "$RELATIVE_PATH/node_modules" node_modules +ln -sf "$RELATIVE_PATH/vendor" vendor + +echo "✓ Shared dependencies linked successfully" +echo " node_modules -> $RELATIVE_PATH/node_modules" +echo " vendor -> $RELATIVE_PATH/vendor" \ No newline at end of file From b847e3801aa1a23d9b9325767efb0b78bf481087 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:06:25 +0100 Subject: [PATCH 005/432] Use main repo directories for shared dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified the worktree setup to use the main repository's node_modules and vendor directories directly instead of creating a separate .shared-deps directory. Changes: - Updated conductor-setup.sh to symlink directly to main repo's directories - Updated CLAUDE.md to reflect the simpler approach - Symlinks now point to ../../node_modules and ../../vendor Benefits: - Simpler setup with no extra directories - All worktrees share the main repo's dependencies - No need to add .shared-deps to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 16 +++++++--------- scripts/conductor-setup.sh | 15 +++++++-------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e8c040402..3982ec81c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,8 @@ This repository uses git worktrees for parallel development with **automatic sha ### How It Works The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically: -1. Creates a shared `.shared-deps/` directory in the main repository -2. Creates symlinks from `node_modules` and `vendor` to the shared location +1. Creates symlinks from worktree's `node_modules` and `vendor` to the main repository's directories +2. All worktrees share the same dependencies from the main repository 3. This happens automatically when Conductor creates a new worktree ### Benefits @@ -27,6 +27,7 @@ The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically: - **Faster setup**: No need to run `npm install` or `composer install` for each worktree - **Consistent versions**: All worktrees use the same dependency versions - **Auto-configured**: Handled by Conductor's setup script +- **Simple**: Uses the main repo's existing directories, no extra folders ### Manual Setup (If Needed) @@ -34,18 +35,15 @@ If you need to set up symlinks manually or for non-Conductor worktrees: ```bash # From the worktree directory -SHARED_DEPS="../../.shared-deps" -mkdir -p "$SHARED_DEPS/node_modules" "$SHARED_DEPS/vendor" rm -rf node_modules vendor -ln -sf "$SHARED_DEPS/node_modules" node_modules -ln -sf "$SHARED_DEPS/vendor" vendor +ln -sf ../../node_modules node_modules +ln -sf ../../vendor vendor ``` ### Important Notes -- Dependencies are shared at `$CONDUCTOR_ROOT_PATH/.shared-deps/` -- Run `npm install` or `composer install` from any worktree to update all -- Ensure `.shared-deps/` is in `.gitignore` (should already be there) +- Dependencies are shared from the main repository (`$CONDUCTOR_ROOT_PATH`) +- Run `npm install` or `composer install` from the main repo or any worktree to update all - If different branches need different dependency versions, this won't work - remove symlinks and use separate directories ## Development Commands diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index 963c8e808..a1172e0d0 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -4,23 +4,22 @@ set -e # Copy .env file cp $CONDUCTOR_ROOT_PATH/.env .env -# Setup shared dependencies via symlinks +# Setup shared dependencies via symlinks to main repo echo "Setting up shared node_modules and vendor directories..." -# Create shared-deps directory in main repository if it doesn't exist -SHARED_DEPS="$CONDUCTOR_ROOT_PATH/.shared-deps" -mkdir -p "$SHARED_DEPS/node_modules" -mkdir -p "$SHARED_DEPS/vendor" +# Ensure main repo has the directories +mkdir -p "$CONDUCTOR_ROOT_PATH/node_modules" +mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Remove existing directories if they exist and are not symlinks [ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules [ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor -# Calculate relative path from worktree to shared deps +# Calculate relative path from worktree to main repo WORKTREE_PATH=$(pwd) -RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$SHARED_DEPS', '$WORKTREE_PATH'))") +RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") -# Create symlinks +# Create symlinks to main repo's node_modules and vendor ln -sf "$RELATIVE_PATH/node_modules" node_modules ln -sf "$RELATIVE_PATH/vendor" vendor From 4507d99460982f2a2516a5b7551360c371f0e575 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:15:03 +0100 Subject: [PATCH 006/432] Use absolute paths in rm commands for safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed rm -rf commands to use absolute paths ($WORKTREE_PATH) instead of relative paths to prevent accidental deletion if symlinks behave unexpectedly. Also cleaned up duplicate WORKTREE_PATH definition. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index a1172e0d0..b7e8ccb36 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -11,12 +11,14 @@ echo "Setting up shared node_modules and vendor directories..." mkdir -p "$CONDUCTOR_ROOT_PATH/node_modules" mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" +# Get current worktree path +WORKTREE_PATH=$(pwd) + # Remove existing directories if they exist and are not symlinks -[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf node_modules -[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf vendor +[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" +[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" # Calculate relative path from worktree to main repo -WORKTREE_PATH=$(pwd) RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") # Create symlinks to main repo's node_modules and vendor From c6316272003917fa233c7ab1f00b774a74bf205e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:18:24 +0100 Subject: [PATCH 007/432] Add safety checks to prevent dangerous deletions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added multiple safety validations before executing rm -rf commands: - Check WORKTREE_PATH is not empty, /, /Users, or $HOME - Verify we're actually in a git repository (.git exists) This prevents accidental deletion of critical directories if the script is run in the wrong location or with unexpected environment variables. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index b7e8ccb36..effad78fc 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -14,6 +14,18 @@ mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Get current worktree path WORKTREE_PATH=$(pwd) +# Safety check: ensure WORKTREE_PATH is valid and not a dangerous location +if [ -z "$WORKTREE_PATH" ] || [ "$WORKTREE_PATH" = "/" ] || [ "$WORKTREE_PATH" = "/Users" ] || [ "$WORKTREE_PATH" = "$HOME" ]; then + echo "ERROR: Invalid or dangerous WORKTREE_PATH: $WORKTREE_PATH" + exit 1 +fi + +# Additional safety: ensure we're in a git worktree +if [ ! -f ".git" ] && [ ! -d ".git" ]; then + echo "ERROR: Not in a git repository" + exit 1 +fi + # Remove existing directories if they exist and are not symlinks [ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" [ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" From ff7b27be61217742524bf4f5161da068e59fce55 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:58:21 +0100 Subject: [PATCH 008/432] Improve worktree setup safety and cross-platform compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add validation for CONDUCTOR_ROOT_PATH environment variable - Enhance safety checks with explicit blacklist of system directories - Improve directory detection (symlink vs regular directory) - Replace Python dependency with cross-platform bash+perl for path calculation - Use absolute paths consistently to prevent symlink following - Add detailed comments explaining each safety check 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/conductor-setup.sh | 73 +++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/scripts/conductor-setup.sh b/scripts/conductor-setup.sh index effad78fc..a88b457fb 100755 --- a/scripts/conductor-setup.sh +++ b/scripts/conductor-setup.sh @@ -1,8 +1,20 @@ #!/bin/bash set -e +# Validate CONDUCTOR_ROOT_PATH is set and valid before any operations +if [ -z "$CONDUCTOR_ROOT_PATH" ]; then + echo "ERROR: CONDUCTOR_ROOT_PATH environment variable is not set" + echo "This script must be run by Conductor with CONDUCTOR_ROOT_PATH set to the main repository path" + exit 1 +fi + +if [ ! -d "$CONDUCTOR_ROOT_PATH" ]; then + echo "ERROR: CONDUCTOR_ROOT_PATH ($CONDUCTOR_ROOT_PATH) is not a valid directory" + exit 1 +fi + # Copy .env file -cp $CONDUCTOR_ROOT_PATH/.env .env +cp "$CONDUCTOR_ROOT_PATH/.env" .env # Setup shared dependencies via symlinks to main repo echo "Setting up shared node_modules and vendor directories..." @@ -14,24 +26,67 @@ mkdir -p "$CONDUCTOR_ROOT_PATH/vendor" # Get current worktree path WORKTREE_PATH=$(pwd) -# Safety check: ensure WORKTREE_PATH is valid and not a dangerous location -if [ -z "$WORKTREE_PATH" ] || [ "$WORKTREE_PATH" = "/" ] || [ "$WORKTREE_PATH" = "/Users" ] || [ "$WORKTREE_PATH" = "$HOME" ]; then - echo "ERROR: Invalid or dangerous WORKTREE_PATH: $WORKTREE_PATH" +# Safety check 1: ensure WORKTREE_PATH is valid +if [ -z "$WORKTREE_PATH" ]; then + echo "ERROR: WORKTREE_PATH is empty" exit 1 fi -# Additional safety: ensure we're in a git worktree +# Safety check 2: CRITICAL FIRST - blacklist system directories +# This check runs BEFORE the positive check to prevent dangerous operations +# even if someone misconfigures CONDUCTOR_ROOT_PATH +case "$WORKTREE_PATH" in + /|/bin|/sbin|/usr|/usr/*|/etc|/etc/*|/var|/var/*|/System|/System/*|/Library|/Library/*|/Applications|/Applications/*|"$HOME") + echo "ERROR: WORKTREE_PATH ($WORKTREE_PATH) is in a dangerous system location" + exit 1 + ;; +esac + +# Safety check 3: positive check - verify we're under CONDUCTOR_ROOT_PATH +case "$WORKTREE_PATH" in + "$CONDUCTOR_ROOT_PATH"|"$CONDUCTOR_ROOT_PATH"/.conductor/*) + # Valid: either main repo or under .conductor/ + ;; + *) + echo "ERROR: WORKTREE_PATH ($WORKTREE_PATH) is not under CONDUCTOR_ROOT_PATH ($CONDUCTOR_ROOT_PATH)" + exit 1 + ;; +esac + +# Safety check 4: verify we're in a git repository if [ ! -f ".git" ] && [ ! -d ".git" ]; then echo "ERROR: Not in a git repository" exit 1 fi -# Remove existing directories if they exist and are not symlinks -[ -d "node_modules" ] && [ ! -L "node_modules" ] && rm -rf "$WORKTREE_PATH/node_modules" -[ -d "vendor" ] && [ ! -L "vendor" ] && rm -rf "$WORKTREE_PATH/vendor" +# Remove existing directories/symlinks if they exist +# For symlinks: use 'rm' without -r to remove the symlink itself (not following it) +# For directories: use 'rm -rf' to remove the directory and contents +if [ -L "node_modules" ]; then + # It's a symlink - remove it without following (no -r flag) + rm "$WORKTREE_PATH/node_modules" +elif [ -e "node_modules" ]; then + # It's a regular directory or file - safe to use -rf + rm -rf "$WORKTREE_PATH/node_modules" +fi + +if [ -L "vendor" ]; then + # It's a symlink - remove it without following (no -r flag) + rm "$WORKTREE_PATH/vendor" +elif [ -e "vendor" ]; then + # It's a regular directory or file - safe to use -rf + rm -rf "$WORKTREE_PATH/vendor" +fi # Calculate relative path from worktree to main repo -RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('$CONDUCTOR_ROOT_PATH', '$WORKTREE_PATH'))") +# Use bash-native approach: try realpath first (GNU coreutils), fallback to perl +if command -v realpath &> /dev/null && realpath --relative-to / / &> /dev/null 2>&1; then + # GNU coreutils realpath with --relative-to support + RELATIVE_PATH=$(realpath --relative-to="$WORKTREE_PATH" "$CONDUCTOR_ROOT_PATH") +else + # Fallback: use perl which is standard on macOS and most Unix systems + RELATIVE_PATH=$(perl -e 'use File::Spec; print File::Spec->abs2rel($ARGV[0], $ARGV[1])' "$CONDUCTOR_ROOT_PATH" "$WORKTREE_PATH") +fi # Create symlinks to main repo's node_modules and vendor ln -sf "$RELATIVE_PATH/node_modules" node_modules From 5ee8015a0fc7cea3c0602a00a71bcb027db33268 Mon Sep 17 00:00:00 2001 From: Praveen Pendyala Date: Tue, 25 Nov 2025 13:14:09 +0000 Subject: [PATCH 009/432] Add Transcript LOL link and logo to README My account is a small sponsor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a84b3bfa9..276ef07b5 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ Thank you so much! Niklas Lausch Cap-go InterviewPal +Transcript LOL ...and many more at [GitHub Sponsors](https://github.com/sponsors/coollabsio) From b72f39b7ac66950c03611a26022fbab839cba8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Tue, 2 Dec 2025 18:56:06 +0100 Subject: [PATCH 010/432] migrate openpanel to v2 --- templates/compose/openpanel.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/compose/openpanel.yaml b/templates/compose/openpanel.yaml index 676ae0356..3a9bfa844 100644 --- a/templates/compose/openpanel.yaml +++ b/templates/compose/openpanel.yaml @@ -7,13 +7,13 @@ services: openpanel-dashboard: - image: lindesvard/openpanel-dashboard:latest + image: lindesvard/openpanel-dashboard:2.0.0 environment: - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true + - SELF_HOSTED=true - SERVICE_URL_OPDASHBOARD_3000 - - NEXT_PUBLIC_API_URL=${SERVICE_URL_OPAPI} - - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_URL_OPDASHBOARD} + - API_URL=${SERVICE_URL_OPAPI} + - DASHBOARD_URL=${SERVICE_URL_OPDASHBOARD} - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 - CLICKHOUSE_URL=http://clickhouse:8123/openpanel @@ -37,7 +37,7 @@ services: start_period: 15s openpanel-api: - image: lindesvard/openpanel-api:latest + image: lindesvard/openpanel-api:2.0.0 command: > sh -c " echo 'Running migrations...' @@ -47,10 +47,10 @@ services: " environment: - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true + - SELF_HOSTED=true - SERVICE_URL_OPAPI - - NEXT_PUBLIC_API_URL=${SERVICE_URL_OPAPI} - - NEXT_PUBLIC_DASHBOARD_URL=${SERVICE_URL_OPDASHBOARD} + - API_URL=${SERVICE_URL_OPAPI} + - DASHBOARD_URL=${SERVICE_URL_OPDASHBOARD} - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 @@ -74,13 +74,13 @@ services: retries: 5 openpanel-worker: - image: lindesvard/openpanel-worker:latest + image: lindesvard/openpanel-worker:2.0.0 environment: - DISABLE_BULLBOARD=${DISABLE_BULLBOARD:-1} - NODE_ENV=production - - NEXT_PUBLIC_SELF_HOSTED=true + - SELF_HOSTED=true - SERVICE_URL_OPBULLBOARD - - NEXT_PUBLIC_API_URL=${SERVICE_URL_OPAPI} + - API_URL=${SERVICE_URL_OPAPI} - DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - DATABASE_URL_DIRECT=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${OPENPANEL_POSTGRES_DB:-openpanel-db}?schema=public - REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379 @@ -129,7 +129,7 @@ services: retries: 5 clickhouse: - image: clickhouse/clickhouse-server:24.3.2-alpine + image: clickhouse/clickhouse-server:25.10.2.65 volumes: - openpanel_clickhouse_data:/var/lib/clickhouse - openpanel_clickhouse_logs:/var/log/clickhouse-server From 8289dcc3ca057f2e39d15cf7e5f30910002c1a2c Mon Sep 17 00:00:00 2001 From: Murat Aslan Date: Tue, 9 Dec 2025 10:40:19 +0300 Subject: [PATCH 011/432] feat: add ServiceDatabase restore/import support Add support for restoring/importing backups in ServiceDatabase (Docker Compose databases). Changes: - Add ServiceDatabase case in buildRestoreCommand() method - Handle ServiceDatabase container naming in getContainers() - Support PostgreSQL, MySQL, MariaDB, MongoDB detection via databaseType() - Mark unsupported ServiceDatabase types (Redis, KeyDB, etc.) Fixes #7529 --- app/Livewire/Project/Database/Import.php | 59 ++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php index 26feb1a5e..0b6e31ea0 100644 --- a/app/Livewire/Project/Database/Import.php +++ b/app/Livewire/Project/Database/Import.php @@ -176,8 +176,23 @@ class Import extends Component public function updatedDumpAll($value) { - switch ($this->resource->getMorphClass()) { + $morphClass = $this->resource->getMorphClass(); + + // Handle ServiceDatabase by checking the database type + if ($morphClass === \App\Models\ServiceDatabase::class) { + $dbType = $this->resource->databaseType(); + if (str_contains($dbType, 'mysql')) { + $morphClass = 'mysql'; + } elseif (str_contains($dbType, 'mariadb')) { + $morphClass = 'mariadb'; + } elseif (str_contains($dbType, 'postgres')) { + $morphClass = 'postgresql'; + } + } + + switch ($morphClass) { case \App\Models\StandaloneMariadb::class: + case 'mariadb': if ($value === true) { $this->mariadbRestoreCommand = <<<'EOD' for pid in $(mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do @@ -193,6 +208,7 @@ EOD; } break; case \App\Models\StandaloneMysql::class: + case 'mysql': if ($value === true) { $this->mysqlRestoreCommand = <<<'EOD' for pid in $(mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do @@ -208,6 +224,7 @@ EOD; } break; case \App\Models\StandalonePostgresql::class: + case 'postgresql': if ($value === true) { $this->postgresqlRestoreCommand = <<<'EOD' psql -U $POSTGRES_USER -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \ @@ -236,7 +253,14 @@ EOD; $this->authorize('view', $resource); $this->resource = $resource; $this->server = $this->resource->destination->server; - $this->container = $this->resource->uuid; + + // Handle ServiceDatabase container naming + if ($this->resource->getMorphClass() === \App\Models\ServiceDatabase::class) { + $this->container = $this->resource->name . '-' . $this->resource->service->uuid; + } else { + $this->container = $this->resource->uuid; + } + if (str(data_get($this, 'resource.status'))->startsWith('running')) { $this->containers->push($this->container); } @@ -249,6 +273,15 @@ EOD; ) { $this->unsupported = true; } + + // Mark unsupported ServiceDatabase types (Redis, KeyDB, etc.) + if ($this->resource->getMorphClass() === \App\Models\ServiceDatabase::class) { + $dbType = $this->resource->databaseType(); + if (str_contains($dbType, 'redis') || str_contains($dbType, 'keydb') || + str_contains($dbType, 'dragonfly') || str_contains($dbType, 'clickhouse')) { + $this->unsupported = true; + } + } } public function checkFile() @@ -575,8 +608,25 @@ EOD; public function buildRestoreCommand(string $tmpPath): string { - switch ($this->resource->getMorphClass()) { + $morphClass = $this->resource->getMorphClass(); + + // Handle ServiceDatabase by checking the database type + if ($morphClass === \App\Models\ServiceDatabase::class) { + $dbType = $this->resource->databaseType(); + if (str_contains($dbType, 'mysql')) { + $morphClass = 'mysql'; + } elseif (str_contains($dbType, 'mariadb')) { + $morphClass = 'mariadb'; + } elseif (str_contains($dbType, 'postgres')) { + $morphClass = 'postgresql'; + } elseif (str_contains($dbType, 'mongo')) { + $morphClass = 'mongodb'; + } + } + + switch ($morphClass) { case \App\Models\StandaloneMariadb::class: + case 'mariadb': $restoreCommand = $this->mariadbRestoreCommand; if ($this->dumpAll) { $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mariadb -u root -p\$MARIADB_ROOT_PASSWORD"; @@ -585,6 +635,7 @@ EOD; } break; case \App\Models\StandaloneMysql::class: + case 'mysql': $restoreCommand = $this->mysqlRestoreCommand; if ($this->dumpAll) { $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mysql -u root -p\$MYSQL_ROOT_PASSWORD"; @@ -593,6 +644,7 @@ EOD; } break; case \App\Models\StandalonePostgresql::class: + case 'postgresql': $restoreCommand = $this->postgresqlRestoreCommand; if ($this->dumpAll) { $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \$POSTGRES_USER postgres"; @@ -601,6 +653,7 @@ EOD; } break; case \App\Models\StandaloneMongodb::class: + case 'mongodb': $restoreCommand = $this->mongodbRestoreCommand; if ($this->dumpAll === false) { $restoreCommand .= "{$tmpPath}"; From 60683875426a58a0e8a803ce9eb95276fddf87e3 Mon Sep 17 00:00:00 2001 From: Murat Aslan Date: Fri, 12 Dec 2025 11:47:16 +0300 Subject: [PATCH 012/432] feat: add import backup UI for ServiceDatabase Add Import Backup section to ServiceDatabase view for supported database types (PostgreSQL, MySQL, MariaDB, MongoDB). This enables users to import backups directly from the ServiceDatabase configuration page, utilizing the existing Import Livewire component. --- .../livewire/project/service/database.blade.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/views/livewire/project/service/database.blade.php b/resources/views/livewire/project/service/database.blade.php index 1ebb3a44f..eebe78c3f 100644 --- a/resources/views/livewire/project/service/database.blade.php +++ b/resources/views/livewire/project/service/database.blade.php @@ -49,4 +49,19 @@ instantSave="instantSaveLogDrain" id="isLogDrainEnabled" label="Drain Logs" /> + + @php + $dbType = $database->databaseType(); + $supportedTypes = ['mysql', 'mariadb', 'postgres', 'mongo']; + $isSupported = collect($supportedTypes)->contains(fn($type) => str_contains($dbType, $type)); + @endphp + + @if ($isSupported) + @can('update', $database) +
+

Import Backup

+ +
+ @endcan + @endif From 80d432171d8593529654bb5ebb5bc31708392085 Mon Sep 17 00:00:00 2001 From: OZCAP <48883102+OZCAP@users.noreply.github.com> Date: Sat, 13 Dec 2025 10:43:35 +0400 Subject: [PATCH 013/432] fix: use original_server for log drain config in generate_compose_file When build server is enabled, $this->server points to the build server. The log drain configuration check was using $this->server which would incorrectly check the build server's settings instead of the deployment server where the container actually runs. This fix ensures log drain configuration is correctly applied based on the deployment server's settings by using $this->original_server. --- app/Jobs/ApplicationDeploymentJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index cc1a44f9a..7e2cb8e9c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2613,7 +2613,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } else { $docker_compose['services'][$this->container_name]['labels'] = $labels; } - if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { + if ($this->original_server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { $docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration(); } if ($this->application->settings->is_gpu_enabled) { From b8047a6b14ab77dccb9bdc2b5a60b2b455db1b02 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 15 Dec 2025 14:20:36 +0100 Subject: [PATCH 014/432] Infiscal no longer distributes images with the postgres on the tag --- templates/compose/infisical.yaml | 2 +- templates/test-database-detection.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/infisical.yaml b/templates/compose/infisical.yaml index e7e2cfe43..32c5a44b8 100644 --- a/templates/compose/infisical.yaml +++ b/templates/compose/infisical.yaml @@ -8,7 +8,7 @@ services: backend: - image: "infisical/infisical:latest-postgres" + image: "infisical/infisical:latest" environment: - SERVICE_URL_BACKEND_8080 - SITE_URL=${SERVICE_URL_BACKEND_8080} diff --git a/templates/test-database-detection.yaml b/templates/test-database-detection.yaml index c63f92821..d57c1b270 100644 --- a/templates/test-database-detection.yaml +++ b/templates/test-database-detection.yaml @@ -233,7 +233,7 @@ services: # Infisical - secret management with postgres variant application-infisical-postgres: - image: infisical/infisical:latest-postgres + image: infisical/infisical:latest depends_on: database-postgres-main: condition: service_healthy From d1076add4e3435ae80ee29b33859a37279292ce9 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 15 Dec 2025 17:55:25 +0100 Subject: [PATCH 015/432] Pinning to the current latest --- templates/compose/infisical.yaml | 2 +- templates/test-database-detection.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/compose/infisical.yaml b/templates/compose/infisical.yaml index 32c5a44b8..7ca069b53 100644 --- a/templates/compose/infisical.yaml +++ b/templates/compose/infisical.yaml @@ -8,7 +8,7 @@ services: backend: - image: "infisical/infisical:latest" + image: "infisical/infisical:v0.154.6" environment: - SERVICE_URL_BACKEND_8080 - SITE_URL=${SERVICE_URL_BACKEND_8080} diff --git a/templates/test-database-detection.yaml b/templates/test-database-detection.yaml index d57c1b270..f242316b8 100644 --- a/templates/test-database-detection.yaml +++ b/templates/test-database-detection.yaml @@ -233,7 +233,7 @@ services: # Infisical - secret management with postgres variant application-infisical-postgres: - image: infisical/infisical:latest + image: infisical/infisical:v0.154.6 depends_on: database-postgres-main: condition: service_healthy From 327e8181af94af5f8a5d61c40ba7f9abeb22a491 Mon Sep 17 00:00:00 2001 From: Duane Adam Date: Tue, 16 Dec 2025 10:43:18 +0800 Subject: [PATCH 016/432] Add copy logs button with PII/secret sanitization Add a copy button to individual container logs that strips sensitive data before copying to clipboard. Includes sanitization for emails, database URLs with passwords, JWT tokens, API keys, private key blocks, and git access tokens. --- app/Livewire/Project/Shared/GetLogs.php | 5 + bootstrap/helpers/shared.php | 24 ++++ .../project/shared/get-logs.blade.php | 15 +++ tests/Unit/SanitizeLogsForExportTest.php | 103 ++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 tests/Unit/SanitizeLogsForExportTest.php diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php index f57563330..c0ced26ef 100644 --- a/app/Livewire/Project/Shared/GetLogs.php +++ b/app/Livewire/Project/Shared/GetLogs.php @@ -179,6 +179,11 @@ class GetLogs extends Component } } + public function copyLogs(): string + { + return sanitizeLogsForExport($this->outputs); + } + public function render() { return view('livewire.project.shared.get-logs'); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 1066f1a63..2943432ac 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -653,6 +653,30 @@ function removeAnsiColors($text) return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); } +function sanitizeLogsForExport(string $text): string +{ + // Use existing helper for tokens and ANSI codes + $text = remove_iip($text); + + // Database URLs with passwords - must run before email regex to prevent false matches + // (postgres://user:password@host → postgres://user:@host) + $text = preg_replace('/((?:postgres|mysql|mongodb|rediss?|mariadb):\/\/[^:]+:)[^@]+(@)/i', '$1'.REDACTED.'$2', $text); + + // Email addresses + $text = preg_replace('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', REDACTED, $text); + + // Bearer/JWT tokens + $text = preg_replace('/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/i', 'Bearer '.REDACTED, $text); + + // API keys (common patterns) + $text = preg_replace('/(api[_-]?key|apikey|api[_-]?secret|secret[_-]?key)[=:]\s*[\'"]?[A-Za-z0-9\-_]{16,}[\'"]?/i', '$1='.REDACTED, $text); + + // Private key blocks + $text = preg_replace('/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/', REDACTED, $text); + + return $text; +} + function getTopLevelNetworks(Service|Application $resource) { if ($resource->getMorphClass() === \App\Models\Service::class) { diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php index 8504a160f..f9a804aeb 100644 --- a/resources/views/livewire/project/shared/get-logs.blade.php +++ b/resources/views/livewire/project/shared/get-logs.blade.php @@ -235,6 +235,21 @@ d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" /> + - - - - - diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php index 1d6bc169d..8ea329fac 100644 --- a/resources/views/components/resources/breadcrumbs.blade.php +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -18,7 +18,7 @@
  • -