Pommsn commited on
Commit
8f2f679
Β·
verified Β·
1 Parent(s): 9271c2e

Upload 4 files

Browse files
Files changed (3) hide show
  1. Dockerfile +54 -8
  2. README-FIX1.txt +22 -0
  3. start.sh +268 -58
Dockerfile CHANGED
@@ -1,6 +1,20 @@
1
  # ============================================================
2
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
3
- # Core structure kept from the previous HF deployment.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  # ============================================================
5
 
6
  FROM langgenius/dify-web:1.17.0 AS web
@@ -11,10 +25,26 @@ USER root
11
 
12
  RUN apt-get update \
13
  && apt-get install -y --no-install-recommends \
14
- nginx redis-server redis-tools locales curl ffmpeg git \
15
- build-essential cmake pkg-config libcairo2-dev libjpeg62-turbo-dev libgif-dev \
 
 
 
 
 
 
 
 
 
 
 
16
  && rm -rf /var/lib/apt/lists/* \
17
- && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 || true
 
 
 
 
 
18
 
19
  ENV LANG=en_US.UTF-8 \
20
  LC_ALL=en_US.UTF-8 \
@@ -22,14 +52,20 @@ ENV LANG=en_US.UTF-8 \
22
  MALLOC_ARENA_MAX=2 \
23
  NODE_OPTIONS=--max-old-space-size=2048
24
 
25
- COPY --from=web /app/web /app/web
 
 
 
 
 
 
26
  COPY --from=plugin_daemon /app/main /opt/dify-plugin-daemon/main
27
 
28
  COPY nginx.conf /etc/nginx/nginx.conf
29
  COPY start.sh /app/start.sh
30
  COPY hf_state_keeper.py /app/hf_state_keeper.py
31
 
32
- ARG FORCE_REBUILD=20260908_DIFY_1170_HF_FRESH_V1
33
  RUN echo "${FORCE_REBUILD}" > /tmp/dify-build-version
34
 
35
  RUN install -d -o dify -g dify -m 0755 /home/dify \
@@ -45,7 +81,9 @@ RUN mkdir -p \
45
  /app/storage/plugin_packages \
46
  /app/storage/assets \
47
  /app/.tiktoken \
 
48
  /var/log/nginx \
 
49
  /var/lib/nginx/body \
50
  /var/lib/nginx/proxy \
51
  /var/lib/nginx/fastcgi \
@@ -54,11 +92,16 @@ RUN mkdir -p \
54
  /var/cache/nginx \
55
  /run
56
 
57
- RUN chmod +x /app/start.sh /app/hf_state_keeper.py /opt/dify-plugin-daemon/main \
 
 
 
 
58
  && chown -R dify:dify \
59
  /home/dify \
60
  /app/api/storage \
61
  /app/storage \
 
62
  /opt/dify-plugin-daemon \
63
  /var/log/nginx \
64
  /var/lib/nginx \
@@ -71,7 +114,10 @@ RUN su -s /bin/bash dify -c '\
71
  touch /home/dify/.cache/uv/.write-test && \
72
  rm -f /home/dify/.cache/uv/.write-test && \
73
  /usr/local/bin/uv --version && \
74
- echo "UV PERMISSION TEST = OK" \
 
 
 
75
  '
76
 
77
  ENV PORT=7860 \
 
1
  # ============================================================
2
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
3
+ #
4
+ # Keeps the previous compact HF layout:
5
+ # - Dify API 1.17.0
6
+ # - Dify Web 1.17.0
7
+ # - Dify Plugin Daemon 0.6.10-local
8
+ # - Celery worker
9
+ # - Nginx :7860
10
+ # - Neon PostgreSQL
11
+ # - Upstash Redis
12
+ # - Low-transfer HF State Keeper
13
+ #
14
+ # FIX:
15
+ # Dify Web 1.17.0 image no longer stores the runtime at /app/web.
16
+ # Its production image stores the Next/Vinext runtimes under /app/targets
17
+ # and its launcher at /app/entrypoint.sh.
18
  # ============================================================
19
 
20
  FROM langgenius/dify-web:1.17.0 AS web
 
25
 
26
  RUN apt-get update \
27
  && apt-get install -y --no-install-recommends \
28
+ nginx \
29
+ redis-server \
30
+ redis-tools \
31
+ locales \
32
+ curl \
33
+ ffmpeg \
34
+ git \
35
+ build-essential \
36
+ cmake \
37
+ pkg-config \
38
+ libcairo2-dev \
39
+ libjpeg62-turbo-dev \
40
+ libgif-dev \
41
  && rm -rf /var/lib/apt/lists/* \
42
+ && localedef \
43
+ -i en_US \
44
+ -c \
45
+ -f UTF-8 \
46
+ -A /usr/share/locale/locale.alias \
47
+ en_US.UTF-8 || true
48
 
49
  ENV LANG=en_US.UTF-8 \
50
  LC_ALL=en_US.UTF-8 \
 
52
  MALLOC_ARENA_MAX=2 \
53
  NODE_OPTIONS=--max-old-space-size=2048
54
 
55
+ # Dify Web 1.17 production image layout:
56
+ # /app/targets/next
57
+ # /app/targets/vinext
58
+ # /app/entrypoint.sh
59
+ COPY --from=web /app/targets /app/web/targets
60
+ COPY --from=web /app/entrypoint.sh /app/web/entrypoint.sh
61
+
62
  COPY --from=plugin_daemon /app/main /opt/dify-plugin-daemon/main
63
 
64
  COPY nginx.conf /etc/nginx/nginx.conf
65
  COPY start.sh /app/start.sh
66
  COPY hf_state_keeper.py /app/hf_state_keeper.py
67
 
68
+ ARG FORCE_REBUILD=20260908_DIFY_1170_HF_WEBPATH_FIX1
69
  RUN echo "${FORCE_REBUILD}" > /tmp/dify-build-version
70
 
71
  RUN install -d -o dify -g dify -m 0755 /home/dify \
 
81
  /app/storage/plugin_packages \
82
  /app/storage/assets \
83
  /app/.tiktoken \
84
+ /app/web \
85
  /var/log/nginx \
86
+ /var/lib/nginx \
87
  /var/lib/nginx/body \
88
  /var/lib/nginx/proxy \
89
  /var/lib/nginx/fastcgi \
 
92
  /var/cache/nginx \
93
  /run
94
 
95
+ RUN chmod +x \
96
+ /app/start.sh \
97
+ /app/hf_state_keeper.py \
98
+ /app/web/entrypoint.sh \
99
+ /opt/dify-plugin-daemon/main \
100
  && chown -R dify:dify \
101
  /home/dify \
102
  /app/api/storage \
103
  /app/storage \
104
+ /app/web \
105
  /opt/dify-plugin-daemon \
106
  /var/log/nginx \
107
  /var/lib/nginx \
 
114
  touch /home/dify/.cache/uv/.write-test && \
115
  rm -f /home/dify/.cache/uv/.write-test && \
116
  /usr/local/bin/uv --version && \
117
+ node --version && \
118
+ test -f /app/web/entrypoint.sh && \
119
+ test -d /app/web/targets/next && \
120
+ echo "DIFY 1.17 WEB RUNTIME TEST = OK" \
121
  '
122
 
123
  ENV PORT=7860 \
README-FIX1.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Dify 1.17.0 HF FIX 1
2
+
3
+ Cause of previous build error:
4
+ - dify-web:1.17.0 does NOT have /app/web in its production image.
5
+ - Runtime is under /app/targets and launcher is /app/entrypoint.sh.
6
+
7
+ Files to replace:
8
+ - Dockerfile
9
+ - start.sh
10
+
11
+ Unchanged:
12
+ - hf_state_keeper.py (low-transfer version)
13
+ - nginx.conf
14
+ - HF Variables/Secrets
15
+
16
+ Neon:
17
+ - neondb
18
+ - dify_plugin
19
+
20
+ Redis:
21
+ - keep existing Upstash settings
22
+ - REDIS_DB=0
start.sh CHANGED
@@ -1,7 +1,7 @@
1
  #!/bin/bash
2
  # ============================================================
3
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
4
- # Fresh Neon DB + existing Upstash Redis + Low-Transfer State Keeper
5
  # ============================================================
6
 
7
  set -E -o pipefail
@@ -18,21 +18,28 @@ PUBLIC_ORIGIN="${PUBLIC_ORIGIN%/}"
18
  echo "🌍 HF_HOST = ${HF_HOST}"
19
  echo "🌍 PUBLIC_ORIGIN = ${PUBLIC_ORIGIN}"
20
 
 
 
 
21
  export LANG="${LANG:-en_US.UTF-8}"
22
  export LC_ALL="${LC_ALL:-en_US.UTF-8}"
23
  export PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}"
 
24
  export MODE="api"
25
  export DEBUG="false"
26
  export DEPLOY_ENV="${DEPLOY_ENV:-PRODUCTION}"
27
  export EDITION="SELF_HOSTED"
28
  export DB_TYPE="${DB_TYPE:-postgresql}"
 
29
  export DIFY_BIND_ADDRESS="127.0.0.1"
30
  export DIFY_PORT="5001"
31
 
 
 
 
32
  export STORAGE_TYPE="local"
33
  export STORAGE_LOCAL_PATH="/app/api/storage"
34
 
35
- # Dify 1.17 official plugin-daemon local root.
36
  export PLUGIN_STORAGE_TYPE="local"
37
  export PLUGIN_STORAGE_LOCAL_ROOT="/app/storage"
38
  export PLUGIN_WORKING_PATH="${PLUGIN_STORAGE_LOCAL_ROOT}/cwd"
@@ -49,6 +56,9 @@ mkdir -p \
49
  "${PLUGIN_STORAGE_LOCAL_ROOT}/${PLUGIN_PACKAGE_CACHE_PATH}" \
50
  "${PLUGIN_STORAGE_LOCAL_ROOT}/${PLUGIN_MEDIA_CACHE_PATH}"
51
 
 
 
 
52
  export CONSOLE_API_URL="${PUBLIC_ORIGIN}"
53
  export CONSOLE_WEB_URL="${PUBLIC_ORIGIN}"
54
  export APP_API_URL="${PUBLIC_ORIGIN}"
@@ -59,71 +69,84 @@ export INTERNAL_FILES_URL="http://127.0.0.1:5001"
59
  export TRIGGER_URL="${PUBLIC_ORIGIN}"
60
  export ENDPOINT_URL_TEMPLATE="${PUBLIC_ORIGIN}/e/{hook_id}"
61
 
62
- # Dify 1.17 web server-side routing.
63
  export SERVER_CONSOLE_API_URL="http://127.0.0.1:5001"
64
  export SERVER_APP_API_URL="http://127.0.0.1:5001"
65
 
66
  export COOKIE_DOMAIN=""
67
  export NEXT_PUBLIC_COOKIE_DOMAIN=""
 
 
 
 
68
  export NEXT_PUBLIC_DEPLOY_ENV="${DEPLOY_ENV}"
69
  export NEXT_PUBLIC_EDITION="${EDITION}"
70
  export NEXT_PUBLIC_BASE_PATH=""
71
- export NEXT_PUBLIC_API_PREFIX="${CONSOLE_API_URL}/console/api"
72
- export NEXT_PUBLIC_PUBLIC_API_PREFIX="${APP_API_URL}/api"
73
 
74
  export MARKETPLACE_API_URL="${MARKETPLACE_API_URL:-https://marketplace.dify.ai}"
75
  export MARKETPLACE_URL="${MARKETPLACE_URL:-https://marketplace.dify.ai}"
76
- export NEXT_PUBLIC_MARKETPLACE_API_PREFIX="${MARKETPLACE_API_URL}/api/v1"
77
- export NEXT_PUBLIC_MARKETPLACE_URL_PREFIX="${MARKETPLACE_URL}"
78
- export NEXT_PUBLIC_SENTRY_DSN="${SENTRY_DSN:-}"
79
  export NEXT_TELEMETRY_DISABLED="${NEXT_TELEMETRY_DISABLED:-1}"
80
- export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS="${TEXT_GENERATION_TIMEOUT_MS:-60000}"
81
- export NEXT_PUBLIC_CSP_WHITELIST="${CSP_WHITELIST:-}"
82
- export NEXT_PUBLIC_ALLOW_EMBED="${ALLOW_EMBED:-false}"
83
- export NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME="${ALLOW_UNSAFE_DATA_SCHEME:-false}"
 
84
 
 
 
 
85
  export DB_PLUGIN_DATABASE="${DB_PLUGIN_DATABASE:-dify_plugin}"
 
86
  export PLUGIN_DAEMON_PORT="${PLUGIN_DAEMON_PORT:-5002}"
87
  export PLUGIN_DAEMON_URL="http://127.0.0.1:${PLUGIN_DAEMON_PORT}"
88
  export PLUGIN_DIFY_INNER_API_URL="http://127.0.0.1:5001"
 
89
  export PLUGIN_DEBUGGING_HOST="127.0.0.1"
90
  export PLUGIN_DEBUGGING_PORT="${PLUGIN_DEBUGGING_PORT:-5003}"
 
91
  export PLUGIN_MAX_PACKAGE_SIZE="${PLUGIN_MAX_PACKAGE_SIZE:-52428800}"
92
- export PLUGIN_MAX_FILE_SIZE="${PLUGIN_MAX_FILE_SIZE:-52428800}"
93
- export PLUGIN_MODEL_SCHEMA_CACHE_TTL="${PLUGIN_MODEL_SCHEMA_CACHE_TTL:-3600}"
94
  export PLUGIN_PYTHON_ENV_INIT_TIMEOUT="${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120}"
95
  export PLUGIN_MAX_EXECUTION_TIMEOUT="${PLUGIN_MAX_EXECUTION_TIMEOUT:-600}"
96
  export PLUGIN_STDIO_BUFFER_SIZE="${PLUGIN_STDIO_BUFFER_SIZE:-1024}"
97
  export PLUGIN_STDIO_MAX_BUFFER_SIZE="${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880}"
 
98
  export FORCE_VERIFYING_SIGNATURE="${FORCE_VERIFYING_SIGNATURE:-true}"
99
  export MARKETPLACE_ENABLED="${MARKETPLACE_ENABLED:-true}"
100
 
101
  if [ -z "${PLUGIN_DAEMON_KEY:-}" ]; then
102
- export PLUGIN_DAEMON_KEY="$(python - <<'PY'
 
103
  import hashlib, os
104
  secret = os.environ.get("SECRET_KEY", "dify")
105
  print(hashlib.sha256(("plugin-daemon:" + secret).encode()).hexdigest())
106
  PY
107
- )"
108
  fi
109
 
110
  if [ -z "${PLUGIN_DIFY_INNER_API_KEY:-}" ]; then
111
- export PLUGIN_DIFY_INNER_API_KEY="$(python - <<'PY'
 
112
  import hashlib, os
113
  secret = os.environ.get("SECRET_KEY", "dify")
114
  print(hashlib.sha256(("plugin-inner-api:" + secret).encode()).hexdigest())
115
  PY
116
- )"
117
  fi
118
 
119
  export INNER_API_KEY_FOR_PLUGIN="${PLUGIN_DIFY_INNER_API_KEY}"
120
 
 
 
 
121
  if [ -z "${SECRET_KEY:-}" ]; then
122
  echo "❌ SECRET_KEY is missing"
123
  exit 1
124
  fi
125
 
126
- if [ -z "${DB_HOST:-}" ] || [ -z "${DB_DATABASE:-}" ] || [ -z "${DB_USERNAME:-}" ] || [ -z "${DB_PASSWORD:-}" ]; then
 
 
 
127
  echo "❌ Neon DB settings are incomplete"
128
  exit 1
129
  fi
@@ -141,6 +164,9 @@ echo "STORAGE_LOCAL_PATH = ${STORAGE_LOCAL_PATH}"
141
  echo "PLUGIN_STORAGE = ${PLUGIN_STORAGE_LOCAL_ROOT}"
142
  echo "-----------------------------------------"
143
 
 
 
 
144
  echo ""
145
  echo "============================================================"
146
  echo "πŸ’Ύ Restoring HF runtime state from Neon"
@@ -160,19 +186,25 @@ else
160
  exit 1
161
  fi
162
 
 
 
 
163
  LOCAL_REDIS_ACTIVE=0
164
  REDIS_MODE="UNKNOWN"
165
 
166
  test_redis_connection() {
167
  python - <<'PY'
168
  import os, ssl, sys, redis
 
169
  host = os.environ.get("REDIS_HOST", "")
170
  port = int(os.environ.get("REDIS_PORT", "6379"))
171
  username = os.environ.get("REDIS_USERNAME", "") or None
172
  password = os.environ.get("REDIS_PASSWORD", "") or None
173
- use_ssl = os.environ.get("REDIS_USE_SSL", "false").strip().lower() in ("true", "1", "yes", "on")
 
174
  if not host:
175
  sys.exit(1)
 
176
  kwargs = {
177
  "host": host,
178
  "port": port,
@@ -182,11 +214,17 @@ kwargs = {
182
  "socket_connect_timeout": 5,
183
  "socket_timeout": 5,
184
  }
 
185
  if use_ssl:
186
  kwargs["ssl"] = True
187
  cert_value = os.environ.get("REDIS_SSL_CERT_REQS", "CERT_NONE").upper()
188
- cert_map = {"CERT_NONE": ssl.CERT_NONE, "CERT_OPTIONAL": ssl.CERT_OPTIONAL, "CERT_REQUIRED": ssl.CERT_REQUIRED}
 
 
 
 
189
  kwargs["ssl_cert_reqs"] = cert_map.get(cert_value, ssl.CERT_NONE)
 
190
  try:
191
  client = redis.Redis(**kwargs)
192
  client.ping()
@@ -204,6 +242,7 @@ echo "πŸ”Ž Checking Redis"
204
  echo "============================================================"
205
 
206
  EXTERNAL_REDIS_OK=0
 
207
  if [ -n "${REDIS_HOST:-}" ]; then
208
  for attempt in 1 2 3 4 5; do
209
  echo "Redis attempt ${attempt}/5"
@@ -221,7 +260,18 @@ if [ "${EXTERNAL_REDIS_OK}" = "1" ]; then
221
  else
222
  echo "⚠️ External Redis unavailable"
223
  echo "⚠️ Starting local Redis fallback"
224
- redis-server --bind 127.0.0.1 --protected-mode yes --port 6379 --save "" --appendonly no --daemonize yes --dir /tmp --pidfile /tmp/dify-local-redis.pid --logfile /tmp/dify-local-redis.log
 
 
 
 
 
 
 
 
 
 
 
225
  export REDIS_HOST="127.0.0.1"
226
  export REDIS_PORT="6379"
227
  export REDIS_USERNAME=""
@@ -231,6 +281,7 @@ else
231
  export REDIS_DB="0"
232
  export REDIS_USE_SENTINEL="false"
233
  export REDIS_USE_CLUSTERS="false"
 
234
  REDIS_MODE="LOCAL_FALLBACK"
235
  LOCAL_REDIS_ACTIVE=1
236
  sleep 2
@@ -243,37 +294,52 @@ fi
243
 
244
  echo "βœ… Redis mode = ${REDIS_MODE}"
245
 
246
- export CELERY_BROKER_URL="$(python - <<'PY'
 
 
 
 
247
  import os
248
  from urllib.parse import quote
 
249
  host = os.environ.get("REDIS_HOST", "127.0.0.1")
250
  port = os.environ.get("REDIS_PORT", "6379")
251
  username = os.environ.get("REDIS_USERNAME", "")
252
  password = os.environ.get("REDIS_PASSWORD", "")
253
  redis_db = os.environ.get("REDIS_DB", "0")
254
- ssl_enabled = os.environ.get("REDIS_USE_SSL", "false").lower() in ("true", "1", "yes", "on")
255
  cert_reqs = os.environ.get("REDIS_SSL_CERT_REQS", "CERT_NONE").upper()
 
256
  scheme = "rediss" if ssl_enabled else "redis"
 
257
  if username:
258
  auth = quote(username, safe="") + ":" + quote(password, safe="") + "@"
259
  elif password:
260
  auth = ":" + quote(password, safe="") + "@"
261
  else:
262
  auth = ""
 
263
  query = f"?ssl_cert_reqs={cert_reqs}" if ssl_enabled else ""
264
  print(f"{scheme}://{auth}{host}:{port}/{redis_db}{query}")
265
  PY
266
  )"
 
267
  export CELERY_BACKEND="redis"
268
 
 
 
 
269
  cd /app/api
 
270
  MIGRATION_VALUE="$(echo "${MIGRATION_ENABLED:-true}" | tr '[:upper:]' '[:lower:]')"
 
271
  case "${MIGRATION_VALUE}" in
272
  true|1|yes|on)
273
  echo ""
274
  echo "============================================================"
275
  echo "πŸ—„οΈ Running Dify 1.17 DB Migration"
276
  echo "============================================================"
 
277
  if flask upgrade-db; then
278
  echo "βœ… Database migration OK"
279
  else
@@ -281,21 +347,25 @@ case "${MIGRATION_VALUE}" in
281
  exit 1
282
  fi
283
  ;;
284
- *) echo "ℹ️ Migration disabled" ;;
 
 
285
  esac
286
 
287
- # Fresh install: do NOT force reset-encrypt-key-pair.
288
  if python /app/hf_state_keeper.py has-private-key; then
289
  echo "βœ… Tenant RSA private key already present/restored"
290
  else
291
- echo "ℹ️ No tenant private.pem yet - normal for a fresh Dify setup"
292
- echo "ℹ️ State Keeper will back it up automatically after setup creates it"
293
  fi
294
 
 
 
 
295
  echo ""
296
  echo "============================================================"
297
  echo "🧠 Starting Dify API :5001"
298
  echo "============================================================"
 
299
  cd /app/api
300
 
301
  gunicorn \
@@ -308,63 +378,100 @@ gunicorn \
308
  --access-logfile "-" \
309
  --error-logfile "-" \
310
  app:app &
 
311
  API_PID=$!
312
 
313
  API_READY=0
314
  for attempt in $(seq 1 120); do
315
- if ! kill -0 "${API_PID}" 2>/dev/null; then echo "❌ API stopped"; exit 1; fi
316
- HTTP_CODE="$(curl --silent --output /dev/null --write-out "%{http_code}" --connect-timeout 2 --max-time 5 http://127.0.0.1:5001/console/api/setup || true)"
 
 
 
 
 
 
 
 
 
317
  case "${HTTP_CODE}" in
318
- 2*|3*|4*) API_READY=1; echo "βœ… API ready HTTP ${HTTP_CODE}"; break ;;
 
 
 
 
319
  esac
320
  sleep 1
321
  done
322
- if [ "${API_READY}" != "1" ]; then echo "❌ API unavailable"; exit 1; fi
323
 
 
 
 
 
 
 
 
 
324
  echo ""
325
  echo "============================================================"
326
  echo "🧩 Starting Plugin Daemon 0.6.10 :5002"
327
  echo "============================================================"
 
328
  (
329
  export DB_DATABASE="${DB_PLUGIN_DATABASE}"
330
  export SERVER_PORT="${PLUGIN_DAEMON_PORT}"
331
  export SERVER_KEY="${PLUGIN_DAEMON_KEY}"
332
  export MAX_PLUGIN_PACKAGE_SIZE="${PLUGIN_MAX_PACKAGE_SIZE}"
333
  export PPROF_ENABLED="false"
 
334
  export DIFY_INNER_API_URL="${PLUGIN_DIFY_INNER_API_URL}"
335
  export DIFY_INNER_API_KEY="${PLUGIN_DIFY_INNER_API_KEY}"
 
336
  export PLUGIN_REMOTE_INSTALLING_HOST="${PLUGIN_DEBUGGING_HOST}"
337
  export PLUGIN_REMOTE_INSTALLING_PORT="${PLUGIN_DEBUGGING_PORT}"
338
  export PLUGIN_WORKING_PATH="${PLUGIN_WORKING_PATH}"
 
339
  export FORCE_VERIFYING_SIGNATURE="${FORCE_VERIFYING_SIGNATURE}"
340
  export PYTHON_ENV_INIT_TIMEOUT="${PLUGIN_PYTHON_ENV_INIT_TIMEOUT}"
341
  export PLUGIN_MAX_EXECUTION_TIMEOUT="${PLUGIN_MAX_EXECUTION_TIMEOUT}"
342
  export PLUGIN_STDIO_BUFFER_SIZE="${PLUGIN_STDIO_BUFFER_SIZE}"
343
  export PLUGIN_STDIO_MAX_BUFFER_SIZE="${PLUGIN_STDIO_MAX_BUFFER_SIZE}"
 
344
  export PLUGIN_STORAGE_TYPE="${PLUGIN_STORAGE_TYPE}"
345
  export PLUGIN_STORAGE_LOCAL_ROOT="${PLUGIN_STORAGE_LOCAL_ROOT}"
346
  export PLUGIN_INSTALLED_PATH="${PLUGIN_INSTALLED_PATH}"
347
  export PLUGIN_PACKAGE_CACHE_PATH="${PLUGIN_PACKAGE_CACHE_PATH}"
348
  export PLUGIN_MEDIA_CACHE_PATH="${PLUGIN_MEDIA_CACHE_PATH}"
 
349
  export MARKETPLACE_ENABLED="${MARKETPLACE_ENABLED}"
350
  export MARKETPLACE_API_URL="${MARKETPLACE_API_URL}"
 
351
  export UV_PATH="/usr/local/bin/uv"
352
  export PYTHON_INTERPRETER_PATH="/usr/local/bin/python3.12"
353
  export PLATFORM="local"
354
  export GIN_MODE="release"
355
  export TIKTOKEN_CACHE_DIR="/app/.tiktoken"
 
356
  exec /opt/dify-plugin-daemon/main
357
  ) &
 
358
  PLUGIN_PID=$!
359
 
360
  PLUGIN_READY=0
361
  for attempt in $(seq 1 120); do
362
- if ! kill -0 "${PLUGIN_PID}" 2>/dev/null; then echo "❌ Plugin Daemon stopped"; break; fi
 
 
 
 
363
  if python - <<'PY'
364
  import socket, sys
365
- sock = socket.socket(); sock.settimeout(1)
 
366
  try:
367
- sock.connect(("127.0.0.1", 5002)); sock.close(); sys.exit(0)
 
 
368
  except Exception:
369
  sys.exit(1)
370
  PY
@@ -373,66 +480,137 @@ PY
373
  echo "βœ… Plugin Daemon ready"
374
  break
375
  fi
 
376
  sleep 1
377
  done
378
- if [ "${PLUGIN_READY}" != "1" ]; then echo "⚠️ Plugin Daemon unavailable"; fi
379
 
 
 
 
 
 
 
 
380
  echo ""
381
  echo "============================================================"
382
  echo "βš™οΈ Starting Celery Worker"
383
  echo "============================================================"
 
384
  (
385
  export MODE="worker"
386
  export MIGRATION_ENABLED="false"
387
  export CELERY_WORKER_AMOUNT="${CELERY_WORKER_AMOUNT:-1}"
388
  export CELERY_AUTO_SCALE="false"
389
- # Experimental 1.17 Agent backend is intentionally not bundled in this compact HF layout.
390
- export AGENT_BACKEND_BASE_URL="${AGENT_BACKEND_BASE_URL:-http://127.0.0.1:5050}"
391
  exec /bin/bash /entrypoint.sh
392
  ) &
 
393
  WORKER_PID=$!
 
394
  sleep 5
395
 
 
 
 
 
 
 
 
 
 
 
396
  echo ""
397
  echo "============================================================"
398
- echo "🌐 Starting Dify Web :3000"
399
  echo "============================================================"
400
- cd /app/web
401
- export NODE_ENV="production"
402
- export PORT="3000"
403
- export HOSTNAME="0.0.0.0"
404
- node server.js &
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  WEB_PID=$!
406
 
407
  WEB_READY=0
408
  for attempt in $(seq 1 120); do
409
- if ! kill -0 "${WEB_PID}" 2>/dev/null; then echo "❌ Web stopped"; exit 1; fi
410
- HTTP_CODE="$(curl --silent --output /dev/null --write-out "%{http_code}" --connect-timeout 2 --max-time 5 http://127.0.0.1:3000/signin || true)"
 
 
 
 
 
 
 
 
 
411
  case "${HTTP_CODE}" in
412
- 2*|3*|4*) WEB_READY=1; echo "βœ… Web ready HTTP ${HTTP_CODE}"; break ;;
 
 
 
 
413
  esac
414
  sleep 1
415
  done
416
- if [ "${WEB_READY}" != "1" ]; then echo "❌ Web unavailable"; exit 1; fi
417
 
 
 
 
 
 
 
 
 
418
  echo ""
419
  echo "============================================================"
420
  echo "🚦 Starting Nginx :7860"
421
  echo "============================================================"
422
- if ! nginx -t; then echo "❌ Nginx configuration invalid"; exit 1; fi
 
 
 
 
 
423
  nginx
424
  sleep 2
425
 
 
 
 
426
  echo ""
427
  echo "============================================================"
428
  echo "πŸ’Ύ Starting Low-Transfer State Keeper"
429
  echo "============================================================"
430
- # No --force: avoid unnecessary large Neon transfers.
431
  python /app/hf_state_keeper.py sync-once || true
432
  python /app/hf_state_keeper.py watch &
433
  STATE_KEEPER_PID=$!
 
434
  sleep 1
435
- if kill -0 "${STATE_KEEPER_PID}" 2>/dev/null; then echo "βœ… State Keeper running"; else echo "❌ State Keeper stopped"; exit 1; fi
 
 
 
 
 
 
436
 
437
  echo ""
438
  echo "============================================================"
@@ -444,31 +622,63 @@ echo "API: READY"
444
  echo "Web: READY"
445
  echo "Plugin Daemon: $([ "${PLUGIN_READY}" = "1" ] && echo READY || echo NOT_READY)"
446
  echo "State Keeper: RUNNING"
447
- echo "Agent backend: NOT INCLUDED (same compact HF structure)"
448
  echo "============================================================"
 
449
  python /app/hf_state_keeper.py status || true
450
 
451
  cleanup() {
452
  echo ""
453
  echo "πŸ›‘ Stopping Dify..."
 
454
  python /app/hf_state_keeper.py sync-once || true
 
455
  nginx -s quit 2>/dev/null || true
 
456
  kill "${STATE_KEEPER_PID}" 2>/dev/null || true
457
  kill "${WEB_PID}" 2>/dev/null || true
458
  kill "${WORKER_PID}" 2>/dev/null || true
459
  kill "${PLUGIN_PID}" 2>/dev/null || true
460
  kill "${API_PID}" 2>/dev/null || true
461
- if [ "${LOCAL_REDIS_ACTIVE}" = "1" ]; then redis-cli -h 127.0.0.1 -p 6379 shutdown nosave >/dev/null 2>&1 || true; fi
 
 
 
462
  }
 
463
  trap cleanup EXIT TERM INT
464
 
465
  PLUGIN_WARNING_SHOWN=0
466
  WORKER_WARNING_SHOWN=0
 
467
  while true; do
468
- if ! kill -0 "${API_PID}" 2>/dev/null; then echo "❌ API stopped"; exit 1; fi
469
- if ! kill -0 "${WEB_PID}" 2>/dev/null; then echo "❌ Web stopped"; exit 1; fi
470
- if ! kill -0 "${STATE_KEEPER_PID}" 2>/dev/null; then echo "❌ State Keeper stopped"; exit 1; fi
471
- if ! kill -0 "${PLUGIN_PID}" 2>/dev/null && [ "${PLUGIN_WARNING_SHOWN}" = "0" ]; then echo "⚠️ Plugin Daemon stopped"; PLUGIN_WARNING_SHOWN=1; fi
472
- if ! kill -0 "${WORKER_PID}" 2>/dev/null && [ "${WORKER_WARNING_SHOWN}" = "0" ]; then echo "⚠️ Worker stopped"; WORKER_WARNING_SHOWN=1; fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  sleep 10
474
  done
 
1
  #!/bin/bash
2
  # ============================================================
3
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
4
+ # Fresh Neon + Upstash Redis + Low-Transfer State Keeper
5
  # ============================================================
6
 
7
  set -E -o pipefail
 
18
  echo "🌍 HF_HOST = ${HF_HOST}"
19
  echo "🌍 PUBLIC_ORIGIN = ${PUBLIC_ORIGIN}"
20
 
21
+ # ============================================================
22
+ # Core runtime
23
+ # ============================================================
24
  export LANG="${LANG:-en_US.UTF-8}"
25
  export LC_ALL="${LC_ALL:-en_US.UTF-8}"
26
  export PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}"
27
+
28
  export MODE="api"
29
  export DEBUG="false"
30
  export DEPLOY_ENV="${DEPLOY_ENV:-PRODUCTION}"
31
  export EDITION="SELF_HOSTED"
32
  export DB_TYPE="${DB_TYPE:-postgresql}"
33
+
34
  export DIFY_BIND_ADDRESS="127.0.0.1"
35
  export DIFY_PORT="5001"
36
 
37
+ # ============================================================
38
+ # Storage
39
+ # ============================================================
40
  export STORAGE_TYPE="local"
41
  export STORAGE_LOCAL_PATH="/app/api/storage"
42
 
 
43
  export PLUGIN_STORAGE_TYPE="local"
44
  export PLUGIN_STORAGE_LOCAL_ROOT="/app/storage"
45
  export PLUGIN_WORKING_PATH="${PLUGIN_STORAGE_LOCAL_ROOT}/cwd"
 
56
  "${PLUGIN_STORAGE_LOCAL_ROOT}/${PLUGIN_PACKAGE_CACHE_PATH}" \
57
  "${PLUGIN_STORAGE_LOCAL_ROOT}/${PLUGIN_MEDIA_CACHE_PATH}"
58
 
59
+ # ============================================================
60
+ # Public URLs
61
+ # ============================================================
62
  export CONSOLE_API_URL="${PUBLIC_ORIGIN}"
63
  export CONSOLE_WEB_URL="${PUBLIC_ORIGIN}"
64
  export APP_API_URL="${PUBLIC_ORIGIN}"
 
69
  export TRIGGER_URL="${PUBLIC_ORIGIN}"
70
  export ENDPOINT_URL_TEMPLATE="${PUBLIC_ORIGIN}/e/{hook_id}"
71
 
 
72
  export SERVER_CONSOLE_API_URL="http://127.0.0.1:5001"
73
  export SERVER_APP_API_URL="http://127.0.0.1:5001"
74
 
75
  export COOKIE_DOMAIN=""
76
  export NEXT_PUBLIC_COOKIE_DOMAIN=""
77
+
78
+ # ============================================================
79
+ # Web runtime environment
80
+ # ============================================================
81
  export NEXT_PUBLIC_DEPLOY_ENV="${DEPLOY_ENV}"
82
  export NEXT_PUBLIC_EDITION="${EDITION}"
83
  export NEXT_PUBLIC_BASE_PATH=""
 
 
84
 
85
  export MARKETPLACE_API_URL="${MARKETPLACE_API_URL:-https://marketplace.dify.ai}"
86
  export MARKETPLACE_URL="${MARKETPLACE_URL:-https://marketplace.dify.ai}"
87
+
 
 
88
  export NEXT_TELEMETRY_DISABLED="${NEXT_TELEMETRY_DISABLED:-1}"
89
+ export EXPERIMENTAL_ENABLE_VINEXT="${EXPERIMENTAL_ENABLE_VINEXT:-false}"
90
+ export TEXT_GENERATION_TIMEOUT_MS="${TEXT_GENERATION_TIMEOUT_MS:-60000}"
91
+ export CSP_WHITELIST="${CSP_WHITELIST:-}"
92
+ export ALLOW_EMBED="${ALLOW_EMBED:-false}"
93
+ export ALLOW_UNSAFE_DATA_SCHEME="${ALLOW_UNSAFE_DATA_SCHEME:-false}"
94
 
95
+ # ============================================================
96
+ # Plugin Daemon
97
+ # ============================================================
98
  export DB_PLUGIN_DATABASE="${DB_PLUGIN_DATABASE:-dify_plugin}"
99
+
100
  export PLUGIN_DAEMON_PORT="${PLUGIN_DAEMON_PORT:-5002}"
101
  export PLUGIN_DAEMON_URL="http://127.0.0.1:${PLUGIN_DAEMON_PORT}"
102
  export PLUGIN_DIFY_INNER_API_URL="http://127.0.0.1:5001"
103
+
104
  export PLUGIN_DEBUGGING_HOST="127.0.0.1"
105
  export PLUGIN_DEBUGGING_PORT="${PLUGIN_DEBUGGING_PORT:-5003}"
106
+
107
  export PLUGIN_MAX_PACKAGE_SIZE="${PLUGIN_MAX_PACKAGE_SIZE:-52428800}"
 
 
108
  export PLUGIN_PYTHON_ENV_INIT_TIMEOUT="${PLUGIN_PYTHON_ENV_INIT_TIMEOUT:-120}"
109
  export PLUGIN_MAX_EXECUTION_TIMEOUT="${PLUGIN_MAX_EXECUTION_TIMEOUT:-600}"
110
  export PLUGIN_STDIO_BUFFER_SIZE="${PLUGIN_STDIO_BUFFER_SIZE:-1024}"
111
  export PLUGIN_STDIO_MAX_BUFFER_SIZE="${PLUGIN_STDIO_MAX_BUFFER_SIZE:-5242880}"
112
+
113
  export FORCE_VERIFYING_SIGNATURE="${FORCE_VERIFYING_SIGNATURE:-true}"
114
  export MARKETPLACE_ENABLED="${MARKETPLACE_ENABLED:-true}"
115
 
116
  if [ -z "${PLUGIN_DAEMON_KEY:-}" ]; then
117
+ export PLUGIN_DAEMON_KEY="$(
118
+ python - <<'PY'
119
  import hashlib, os
120
  secret = os.environ.get("SECRET_KEY", "dify")
121
  print(hashlib.sha256(("plugin-daemon:" + secret).encode()).hexdigest())
122
  PY
123
+ )"
124
  fi
125
 
126
  if [ -z "${PLUGIN_DIFY_INNER_API_KEY:-}" ]; then
127
+ export PLUGIN_DIFY_INNER_API_KEY="$(
128
+ python - <<'PY'
129
  import hashlib, os
130
  secret = os.environ.get("SECRET_KEY", "dify")
131
  print(hashlib.sha256(("plugin-inner-api:" + secret).encode()).hexdigest())
132
  PY
133
+ )"
134
  fi
135
 
136
  export INNER_API_KEY_FOR_PLUGIN="${PLUGIN_DIFY_INNER_API_KEY}"
137
 
138
+ # ============================================================
139
+ # Validate settings
140
+ # ============================================================
141
  if [ -z "${SECRET_KEY:-}" ]; then
142
  echo "❌ SECRET_KEY is missing"
143
  exit 1
144
  fi
145
 
146
+ if [ -z "${DB_HOST:-}" ] || \
147
+ [ -z "${DB_DATABASE:-}" ] || \
148
+ [ -z "${DB_USERNAME:-}" ] || \
149
+ [ -z "${DB_PASSWORD:-}" ]; then
150
  echo "❌ Neon DB settings are incomplete"
151
  exit 1
152
  fi
 
164
  echo "PLUGIN_STORAGE = ${PLUGIN_STORAGE_LOCAL_ROOT}"
165
  echo "-----------------------------------------"
166
 
167
+ # ============================================================
168
+ # State Keeper restore
169
+ # ============================================================
170
  echo ""
171
  echo "============================================================"
172
  echo "πŸ’Ύ Restoring HF runtime state from Neon"
 
186
  exit 1
187
  fi
188
 
189
+ # ============================================================
190
+ # Redis
191
+ # ============================================================
192
  LOCAL_REDIS_ACTIVE=0
193
  REDIS_MODE="UNKNOWN"
194
 
195
  test_redis_connection() {
196
  python - <<'PY'
197
  import os, ssl, sys, redis
198
+
199
  host = os.environ.get("REDIS_HOST", "")
200
  port = int(os.environ.get("REDIS_PORT", "6379"))
201
  username = os.environ.get("REDIS_USERNAME", "") or None
202
  password = os.environ.get("REDIS_PASSWORD", "") or None
203
+ use_ssl = os.environ.get("REDIS_USE_SSL", "false").strip().lower() in ("true","1","yes","on")
204
+
205
  if not host:
206
  sys.exit(1)
207
+
208
  kwargs = {
209
  "host": host,
210
  "port": port,
 
214
  "socket_connect_timeout": 5,
215
  "socket_timeout": 5,
216
  }
217
+
218
  if use_ssl:
219
  kwargs["ssl"] = True
220
  cert_value = os.environ.get("REDIS_SSL_CERT_REQS", "CERT_NONE").upper()
221
+ cert_map = {
222
+ "CERT_NONE": ssl.CERT_NONE,
223
+ "CERT_OPTIONAL": ssl.CERT_OPTIONAL,
224
+ "CERT_REQUIRED": ssl.CERT_REQUIRED,
225
+ }
226
  kwargs["ssl_cert_reqs"] = cert_map.get(cert_value, ssl.CERT_NONE)
227
+
228
  try:
229
  client = redis.Redis(**kwargs)
230
  client.ping()
 
242
  echo "============================================================"
243
 
244
  EXTERNAL_REDIS_OK=0
245
+
246
  if [ -n "${REDIS_HOST:-}" ]; then
247
  for attempt in 1 2 3 4 5; do
248
  echo "Redis attempt ${attempt}/5"
 
260
  else
261
  echo "⚠️ External Redis unavailable"
262
  echo "⚠️ Starting local Redis fallback"
263
+
264
+ redis-server \
265
+ --bind 127.0.0.1 \
266
+ --protected-mode yes \
267
+ --port 6379 \
268
+ --save "" \
269
+ --appendonly no \
270
+ --daemonize yes \
271
+ --dir /tmp \
272
+ --pidfile /tmp/dify-local-redis.pid \
273
+ --logfile /tmp/dify-local-redis.log
274
+
275
  export REDIS_HOST="127.0.0.1"
276
  export REDIS_PORT="6379"
277
  export REDIS_USERNAME=""
 
281
  export REDIS_DB="0"
282
  export REDIS_USE_SENTINEL="false"
283
  export REDIS_USE_CLUSTERS="false"
284
+
285
  REDIS_MODE="LOCAL_FALLBACK"
286
  LOCAL_REDIS_ACTIVE=1
287
  sleep 2
 
294
 
295
  echo "βœ… Redis mode = ${REDIS_MODE}"
296
 
297
+ # ============================================================
298
+ # Celery broker
299
+ # ============================================================
300
+ export CELERY_BROKER_URL="$(
301
+ python - <<'PY'
302
  import os
303
  from urllib.parse import quote
304
+
305
  host = os.environ.get("REDIS_HOST", "127.0.0.1")
306
  port = os.environ.get("REDIS_PORT", "6379")
307
  username = os.environ.get("REDIS_USERNAME", "")
308
  password = os.environ.get("REDIS_PASSWORD", "")
309
  redis_db = os.environ.get("REDIS_DB", "0")
310
+ ssl_enabled = os.environ.get("REDIS_USE_SSL", "false").lower() in ("true","1","yes","on")
311
  cert_reqs = os.environ.get("REDIS_SSL_CERT_REQS", "CERT_NONE").upper()
312
+
313
  scheme = "rediss" if ssl_enabled else "redis"
314
+
315
  if username:
316
  auth = quote(username, safe="") + ":" + quote(password, safe="") + "@"
317
  elif password:
318
  auth = ":" + quote(password, safe="") + "@"
319
  else:
320
  auth = ""
321
+
322
  query = f"?ssl_cert_reqs={cert_reqs}" if ssl_enabled else ""
323
  print(f"{scheme}://{auth}{host}:{port}/{redis_db}{query}")
324
  PY
325
  )"
326
+
327
  export CELERY_BACKEND="redis"
328
 
329
+ # ============================================================
330
+ # Main DB migration
331
+ # ============================================================
332
  cd /app/api
333
+
334
  MIGRATION_VALUE="$(echo "${MIGRATION_ENABLED:-true}" | tr '[:upper:]' '[:lower:]')"
335
+
336
  case "${MIGRATION_VALUE}" in
337
  true|1|yes|on)
338
  echo ""
339
  echo "============================================================"
340
  echo "πŸ—„οΈ Running Dify 1.17 DB Migration"
341
  echo "============================================================"
342
+
343
  if flask upgrade-db; then
344
  echo "βœ… Database migration OK"
345
  else
 
347
  exit 1
348
  fi
349
  ;;
350
+ *)
351
+ echo "ℹ️ Migration disabled"
352
+ ;;
353
  esac
354
 
 
355
  if python /app/hf_state_keeper.py has-private-key; then
356
  echo "βœ… Tenant RSA private key already present/restored"
357
  else
358
+ echo "ℹ️ No tenant private.pem yet - normal for fresh Dify setup"
 
359
  fi
360
 
361
+ # ============================================================
362
+ # Start API
363
+ # ============================================================
364
  echo ""
365
  echo "============================================================"
366
  echo "🧠 Starting Dify API :5001"
367
  echo "============================================================"
368
+
369
  cd /app/api
370
 
371
  gunicorn \
 
378
  --access-logfile "-" \
379
  --error-logfile "-" \
380
  app:app &
381
+
382
  API_PID=$!
383
 
384
  API_READY=0
385
  for attempt in $(seq 1 120); do
386
+ if ! kill -0 "${API_PID}" 2>/dev/null; then
387
+ echo "❌ API stopped"
388
+ exit 1
389
+ fi
390
+
391
+ HTTP_CODE="$(
392
+ curl --silent --output /dev/null --write-out "%{http_code}" \
393
+ --connect-timeout 2 --max-time 5 \
394
+ http://127.0.0.1:5001/console/api/setup || true
395
+ )"
396
+
397
  case "${HTTP_CODE}" in
398
+ 2*|3*|4*)
399
+ API_READY=1
400
+ echo "βœ… API ready HTTP ${HTTP_CODE}"
401
+ break
402
+ ;;
403
  esac
404
  sleep 1
405
  done
 
406
 
407
+ if [ "${API_READY}" != "1" ]; then
408
+ echo "❌ API unavailable"
409
+ exit 1
410
+ fi
411
+
412
+ # ============================================================
413
+ # Plugin Daemon
414
+ # ============================================================
415
  echo ""
416
  echo "============================================================"
417
  echo "🧩 Starting Plugin Daemon 0.6.10 :5002"
418
  echo "============================================================"
419
+
420
  (
421
  export DB_DATABASE="${DB_PLUGIN_DATABASE}"
422
  export SERVER_PORT="${PLUGIN_DAEMON_PORT}"
423
  export SERVER_KEY="${PLUGIN_DAEMON_KEY}"
424
  export MAX_PLUGIN_PACKAGE_SIZE="${PLUGIN_MAX_PACKAGE_SIZE}"
425
  export PPROF_ENABLED="false"
426
+
427
  export DIFY_INNER_API_URL="${PLUGIN_DIFY_INNER_API_URL}"
428
  export DIFY_INNER_API_KEY="${PLUGIN_DIFY_INNER_API_KEY}"
429
+
430
  export PLUGIN_REMOTE_INSTALLING_HOST="${PLUGIN_DEBUGGING_HOST}"
431
  export PLUGIN_REMOTE_INSTALLING_PORT="${PLUGIN_DEBUGGING_PORT}"
432
  export PLUGIN_WORKING_PATH="${PLUGIN_WORKING_PATH}"
433
+
434
  export FORCE_VERIFYING_SIGNATURE="${FORCE_VERIFYING_SIGNATURE}"
435
  export PYTHON_ENV_INIT_TIMEOUT="${PLUGIN_PYTHON_ENV_INIT_TIMEOUT}"
436
  export PLUGIN_MAX_EXECUTION_TIMEOUT="${PLUGIN_MAX_EXECUTION_TIMEOUT}"
437
  export PLUGIN_STDIO_BUFFER_SIZE="${PLUGIN_STDIO_BUFFER_SIZE}"
438
  export PLUGIN_STDIO_MAX_BUFFER_SIZE="${PLUGIN_STDIO_MAX_BUFFER_SIZE}"
439
+
440
  export PLUGIN_STORAGE_TYPE="${PLUGIN_STORAGE_TYPE}"
441
  export PLUGIN_STORAGE_LOCAL_ROOT="${PLUGIN_STORAGE_LOCAL_ROOT}"
442
  export PLUGIN_INSTALLED_PATH="${PLUGIN_INSTALLED_PATH}"
443
  export PLUGIN_PACKAGE_CACHE_PATH="${PLUGIN_PACKAGE_CACHE_PATH}"
444
  export PLUGIN_MEDIA_CACHE_PATH="${PLUGIN_MEDIA_CACHE_PATH}"
445
+
446
  export MARKETPLACE_ENABLED="${MARKETPLACE_ENABLED}"
447
  export MARKETPLACE_API_URL="${MARKETPLACE_API_URL}"
448
+
449
  export UV_PATH="/usr/local/bin/uv"
450
  export PYTHON_INTERPRETER_PATH="/usr/local/bin/python3.12"
451
  export PLATFORM="local"
452
  export GIN_MODE="release"
453
  export TIKTOKEN_CACHE_DIR="/app/.tiktoken"
454
+
455
  exec /opt/dify-plugin-daemon/main
456
  ) &
457
+
458
  PLUGIN_PID=$!
459
 
460
  PLUGIN_READY=0
461
  for attempt in $(seq 1 120); do
462
+ if ! kill -0 "${PLUGIN_PID}" 2>/dev/null; then
463
+ echo "❌ Plugin Daemon stopped"
464
+ break
465
+ fi
466
+
467
  if python - <<'PY'
468
  import socket, sys
469
+ sock = socket.socket()
470
+ sock.settimeout(1)
471
  try:
472
+ sock.connect(("127.0.0.1", 5002))
473
+ sock.close()
474
+ sys.exit(0)
475
  except Exception:
476
  sys.exit(1)
477
  PY
 
480
  echo "βœ… Plugin Daemon ready"
481
  break
482
  fi
483
+
484
  sleep 1
485
  done
 
486
 
487
+ if [ "${PLUGIN_READY}" != "1" ]; then
488
+ echo "⚠️ Plugin Daemon unavailable"
489
+ fi
490
+
491
+ # ============================================================
492
+ # Celery worker
493
+ # ============================================================
494
  echo ""
495
  echo "============================================================"
496
  echo "βš™οΈ Starting Celery Worker"
497
  echo "============================================================"
498
+
499
  (
500
  export MODE="worker"
501
  export MIGRATION_ENABLED="false"
502
  export CELERY_WORKER_AMOUNT="${CELERY_WORKER_AMOUNT:-1}"
503
  export CELERY_AUTO_SCALE="false"
 
 
504
  exec /bin/bash /entrypoint.sh
505
  ) &
506
+
507
  WORKER_PID=$!
508
+
509
  sleep 5
510
 
511
+ # ============================================================
512
+ # Web 1.17
513
+ #
514
+ # IMPORTANT:
515
+ # 1.17 web image runtime lives in /app/targets, not /app/web.
516
+ # Dockerfile copies it to /app/web/targets and copies the official
517
+ # /app/entrypoint.sh to /app/web/entrypoint.sh.
518
+ # Use Dify's official web entrypoint rather than assuming server.js
519
+ # is directly at /app/web/server.js.
520
+ # ============================================================
521
  echo ""
522
  echo "============================================================"
523
+ echo "🌐 Starting Dify Web 1.17 :3000"
524
  echo "============================================================"
525
+
526
+ (
527
+ cd /app/web
528
+
529
+ export NODE_ENV="production"
530
+ export PORT="3000"
531
+ export HOSTNAME="0.0.0.0"
532
+ export CONSOLE_API_URL="${PUBLIC_ORIGIN}"
533
+ export SERVER_CONSOLE_API_URL="http://127.0.0.1:5001"
534
+ export APP_API_URL="${PUBLIC_ORIGIN}"
535
+ export MARKETPLACE_API_URL="${MARKETPLACE_API_URL}"
536
+ export MARKETPLACE_URL="${MARKETPLACE_URL}"
537
+ export NEXT_PUBLIC_COOKIE_DOMAIN=""
538
+ export NEXT_TELEMETRY_DISABLED="${NEXT_TELEMETRY_DISABLED:-1}"
539
+ export EXPERIMENTAL_ENABLE_VINEXT="${EXPERIMENTAL_ENABLE_VINEXT:-false}"
540
+ export TEXT_GENERATION_TIMEOUT_MS="${TEXT_GENERATION_TIMEOUT_MS:-60000}"
541
+ export CSP_WHITELIST="${CSP_WHITELIST:-}"
542
+ export ALLOW_EMBED="${ALLOW_EMBED:-false}"
543
+ export ALLOW_UNSAFE_DATA_SCHEME="${ALLOW_UNSAFE_DATA_SCHEME:-false}"
544
+
545
+ exec /bin/sh /app/web/entrypoint.sh
546
+ ) &
547
+
548
  WEB_PID=$!
549
 
550
  WEB_READY=0
551
  for attempt in $(seq 1 120); do
552
+ if ! kill -0 "${WEB_PID}" 2>/dev/null; then
553
+ echo "❌ Web stopped"
554
+ exit 1
555
+ fi
556
+
557
+ HTTP_CODE="$(
558
+ curl --silent --output /dev/null --write-out "%{http_code}" \
559
+ --connect-timeout 2 --max-time 5 \
560
+ http://127.0.0.1:3000/signin || true
561
+ )"
562
+
563
  case "${HTTP_CODE}" in
564
+ 2*|3*|4*)
565
+ WEB_READY=1
566
+ echo "βœ… Web ready HTTP ${HTTP_CODE}"
567
+ break
568
+ ;;
569
  esac
570
  sleep 1
571
  done
 
572
 
573
+ if [ "${WEB_READY}" != "1" ]; then
574
+ echo "❌ Web unavailable"
575
+ exit 1
576
+ fi
577
+
578
+ # ============================================================
579
+ # Nginx
580
+ # ============================================================
581
  echo ""
582
  echo "============================================================"
583
  echo "🚦 Starting Nginx :7860"
584
  echo "============================================================"
585
+
586
+ if ! nginx -t; then
587
+ echo "❌ Nginx configuration invalid"
588
+ exit 1
589
+ fi
590
+
591
  nginx
592
  sleep 2
593
 
594
+ # ============================================================
595
+ # Low-transfer State Keeper
596
+ # ============================================================
597
  echo ""
598
  echo "============================================================"
599
  echo "πŸ’Ύ Starting Low-Transfer State Keeper"
600
  echo "============================================================"
601
+
602
  python /app/hf_state_keeper.py sync-once || true
603
  python /app/hf_state_keeper.py watch &
604
  STATE_KEEPER_PID=$!
605
+
606
  sleep 1
607
+
608
+ if kill -0 "${STATE_KEEPER_PID}" 2>/dev/null; then
609
+ echo "βœ… State Keeper running"
610
+ else
611
+ echo "❌ State Keeper stopped"
612
+ exit 1
613
+ fi
614
 
615
  echo ""
616
  echo "============================================================"
 
622
  echo "Web: READY"
623
  echo "Plugin Daemon: $([ "${PLUGIN_READY}" = "1" ] && echo READY || echo NOT_READY)"
624
  echo "State Keeper: RUNNING"
 
625
  echo "============================================================"
626
+
627
  python /app/hf_state_keeper.py status || true
628
 
629
  cleanup() {
630
  echo ""
631
  echo "πŸ›‘ Stopping Dify..."
632
+
633
  python /app/hf_state_keeper.py sync-once || true
634
+
635
  nginx -s quit 2>/dev/null || true
636
+
637
  kill "${STATE_KEEPER_PID}" 2>/dev/null || true
638
  kill "${WEB_PID}" 2>/dev/null || true
639
  kill "${WORKER_PID}" 2>/dev/null || true
640
  kill "${PLUGIN_PID}" 2>/dev/null || true
641
  kill "${API_PID}" 2>/dev/null || true
642
+
643
+ if [ "${LOCAL_REDIS_ACTIVE}" = "1" ]; then
644
+ redis-cli -h 127.0.0.1 -p 6379 shutdown nosave >/dev/null 2>&1 || true
645
+ fi
646
  }
647
+
648
  trap cleanup EXIT TERM INT
649
 
650
  PLUGIN_WARNING_SHOWN=0
651
  WORKER_WARNING_SHOWN=0
652
+
653
  while true; do
654
+ if ! kill -0 "${API_PID}" 2>/dev/null; then
655
+ echo "❌ API stopped"
656
+ exit 1
657
+ fi
658
+
659
+ if ! kill -0 "${WEB_PID}" 2>/dev/null; then
660
+ echo "❌ Web stopped"
661
+ exit 1
662
+ fi
663
+
664
+ if ! kill -0 "${STATE_KEEPER_PID}" 2>/dev/null; then
665
+ echo "❌ State Keeper stopped"
666
+ exit 1
667
+ fi
668
+
669
+ if ! kill -0 "${PLUGIN_PID}" 2>/dev/null; then
670
+ if [ "${PLUGIN_WARNING_SHOWN}" = "0" ]; then
671
+ echo "⚠️ Plugin Daemon stopped"
672
+ PLUGIN_WARNING_SHOWN=1
673
+ fi
674
+ fi
675
+
676
+ if ! kill -0 "${WORKER_PID}" 2>/dev/null; then
677
+ if [ "${WORKER_WARNING_SHOWN}" = "0" ]; then
678
+ echo "⚠️ Worker stopped"
679
+ WORKER_WARNING_SHOWN=1
680
+ fi
681
+ fi
682
+
683
  sleep 10
684
  done