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

Upload 4 files

Browse files
Files changed (3) hide show
  1. Dockerfile +38 -34
  2. README-FIX2.txt +32 -0
  3. start.sh +336 -54
Dockerfile CHANGED
@@ -1,20 +1,14 @@
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
@@ -28,6 +22,7 @@ RUN apt-get update \
28
  nginx \
29
  redis-server \
30
  redis-tools \
 
31
  locales \
32
  curl \
33
  ffmpeg \
@@ -38,6 +33,7 @@ RUN apt-get update \
38
  libcairo2-dev \
39
  libjpeg62-turbo-dev \
40
  libgif-dev \
 
41
  && rm -rf /var/lib/apt/lists/* \
42
  && localedef \
43
  -i en_US \
@@ -52,26 +48,32 @@ ENV LANG=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 \
72
  && install -d -o dify -g dify -m 0755 /home/dify/.cache \
73
  && install -d -o dify -g dify -m 0755 /home/dify/.cache/uv
74
 
 
75
  RUN mkdir -p \
76
  /app/api/storage \
77
  /app/api/storage/privkeys \
@@ -81,7 +83,6 @@ RUN mkdir -p \
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 \
@@ -95,13 +96,14 @@ RUN mkdir -p \
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 \
@@ -109,15 +111,17 @@ RUN chmod +x \
109
  /run \
110
  /etc/nginx
111
 
112
- RUN su -s /bin/bash dify -c '\
113
- test -w /home/dify/.cache/uv && \
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 \
 
1
  # ============================================================
2
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
3
  #
4
+ # FIX 2:
5
+ # - Keep Dify Web 1.17 runtime at its ORIGINAL absolute paths:
6
+ # /app/targets
7
+ # /app/entrypoint.sh
8
+ # because the official web entrypoint calls
9
+ # /app/targets/next/web/server.js
10
+ # - Add ca-certificates for verified Upstash Redis TLS.
11
+ # - Keep API/Web/Worker/Plugin/Nginx in one HF container.
 
 
 
 
 
 
12
  # ============================================================
13
 
14
  FROM langgenius/dify-web:1.17.0 AS web
 
22
  nginx \
23
  redis-server \
24
  redis-tools \
25
+ ca-certificates \
26
  locales \
27
  curl \
28
  ffmpeg \
 
33
  libcairo2-dev \
34
  libjpeg62-turbo-dev \
35
  libgif-dev \
36
+ && update-ca-certificates \
37
  && rm -rf /var/lib/apt/lists/* \
38
  && localedef \
39
  -i en_US \
 
48
  MALLOC_ARENA_MAX=2 \
49
  NODE_OPTIONS=--max-old-space-size=2048
50
 
51
+ # ------------------------------------------------------------
52
+ # Dify Web 1.17 official runtime layout.
53
+ #
54
+ # IMPORTANT:
55
+ # Do not relocate these into /app/web.
56
+ # /app/entrypoint.sh contains absolute /app/targets/... paths.
57
+ # ------------------------------------------------------------
58
+ COPY --from=web /app/targets /app/targets
59
+ COPY --from=web /app/entrypoint.sh /app/entrypoint.sh
60
+
61
+ # Plugin daemon binary.
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_FIX2
69
  RUN echo "${FORCE_REBUILD}" > /tmp/dify-build-version
70
 
71
+ # Writable uv cache for local plugins.
72
  RUN install -d -o dify -g dify -m 0755 /home/dify \
73
  && install -d -o dify -g dify -m 0755 /home/dify/.cache \
74
  && install -d -o dify -g dify -m 0755 /home/dify/.cache/uv
75
 
76
+ # Runtime storage.
77
  RUN mkdir -p \
78
  /app/api/storage \
79
  /app/api/storage/privkeys \
 
83
  /app/storage/plugin_packages \
84
  /app/storage/assets \
85
  /app/.tiktoken \
 
86
  /var/log/nginx \
87
  /var/lib/nginx \
88
  /var/lib/nginx/body \
 
96
  RUN chmod +x \
97
  /app/start.sh \
98
  /app/hf_state_keeper.py \
99
+ /app/entrypoint.sh \
100
  /opt/dify-plugin-daemon/main \
101
  && chown -R dify:dify \
102
  /home/dify \
103
  /app/api/storage \
104
  /app/storage \
105
+ /app/targets \
106
+ /app/entrypoint.sh \
107
  /opt/dify-plugin-daemon \
108
  /var/log/nginx \
109
  /var/lib/nginx \
 
111
  /run \
112
  /etc/nginx
113
 
114
+ # Fail the Docker build immediately if the 1.17 web runtime was copied wrong.
115
+ RUN test -f /app/entrypoint.sh \
116
+ && test -f /app/targets/next/web/server.js \
117
+ && test -f /etc/ssl/certs/ca-certificates.crt \
118
+ && su -s /bin/bash dify -c '\
119
+ test -w /home/dify/.cache/uv && \
120
+ touch /home/dify/.cache/uv/.write-test && \
121
+ rm -f /home/dify/.cache/uv/.write-test && \
122
+ /usr/local/bin/uv --version && \
123
+ node --version && \
124
+ echo "DIFY 1.17 HF FIX2 BUILD TEST = OK" \
125
  '
126
 
127
  ENV PORT=7860 \
README-FIX2.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Dify 1.17.0 HF FIX 2
2
+
3
+ What this fixes
4
+ ---------------
5
+ 1) Web runtime
6
+ Official Dify 1.17 web entrypoint uses absolute paths under /app/targets.
7
+ This package keeps /app/targets and /app/entrypoint.sh exactly there.
8
+
9
+ 2) Plugin database
10
+ start.sh checks for DB_PLUGIN_DATABASE (default dify_plugin) and creates it
11
+ automatically on Neon when the current DB user has permission.
12
+
13
+ 3) Redis TLS
14
+ If REDIS_SSL_CERT_REQS=CERT_REQUIRED and REDIS_SSL_CA_CERTS is unset,
15
+ start.sh uses:
16
+ /etc/ssl/certs/ca-certificates.crt
17
+ No new HF Secret is required.
18
+
19
+ Files to replace
20
+ ----------------
21
+ - Dockerfile
22
+ - start.sh
23
+
24
+ Keep unchanged
25
+ --------------
26
+ - nginx.conf
27
+ - hf_state_keeper.py (low-transfer version)
28
+ - existing HF DB/Redis Secrets
29
+ - REDIS_DB=0
30
+
31
+ No need to drop neondb again:
32
+ the fresh Dify 1.17 migration already completed successfully in the latest log.
start.sh CHANGED
@@ -1,14 +1,22 @@
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
8
 
9
  echo ""
10
  echo "============================================================"
11
- echo "πŸš€ Dify 1.17.0 - HF All-in-One + Low-Transfer State Keeper"
12
  echo "============================================================"
13
 
14
  HF_HOST="${SPACE_HOST:-pommsn-dify.hf.space}"
@@ -19,7 +27,7 @@ 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}"
@@ -35,7 +43,7 @@ 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"
@@ -57,7 +65,7 @@ mkdir -p \
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}"
@@ -76,7 +84,7 @@ 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}"
@@ -87,13 +95,14 @@ 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
 
@@ -116,7 +125,9 @@ export MARKETPLACE_ENABLED="${MARKETPLACE_ENABLED:-true}"
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
@@ -126,7 +137,9 @@ fi
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
@@ -136,7 +149,7 @@ fi
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"
@@ -165,7 +178,7 @@ echo "PLUGIN_STORAGE = ${PLUGIN_STORAGE_LOCAL_ROOT}"
165
  echo "-----------------------------------------"
166
 
167
  # ============================================================
168
- # State Keeper restore
169
  # ============================================================
170
  echo ""
171
  echo "============================================================"
@@ -187,20 +200,39 @@ else
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)
@@ -217,21 +249,44 @@ kwargs = {
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()
231
- print(f"βœ… Redis PING OK: {host}:{port} ssl={use_ssl}")
 
 
 
 
 
232
  sys.exit(0)
 
233
  except Exception as exc:
234
- print(f"❌ Redis connection failed: {type(exc).__name__}: {exc}")
 
 
 
 
235
  sys.exit(1)
236
  PY
237
  }
@@ -244,20 +299,29 @@ echo "============================================================"
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"
 
249
  if test_redis_connection; then
250
  EXTERNAL_REDIS_OK=1
251
  break
252
  fi
 
253
  sleep 3
254
  done
 
255
  fi
256
 
257
  if [ "${EXTERNAL_REDIS_OK}" = "1" ]; then
 
258
  REDIS_MODE="EXTERNAL"
259
  echo "βœ… Using external Redis"
 
260
  else
 
 
261
  echo "⚠️ External Redis unavailable"
262
  echo "⚠️ Starting local Redis fallback"
263
 
@@ -278,13 +342,16 @@ else
278
  export REDIS_PASSWORD=""
279
  export REDIS_USE_SSL="false"
280
  export REDIS_SSL_CERT_REQS="CERT_NONE"
 
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
 
288
  fi
289
 
290
  if ! test_redis_connection; then
@@ -295,7 +362,7 @@ fi
295
  echo "βœ… Redis mode = ${REDIS_MODE}"
296
 
297
  # ============================================================
298
- # Celery broker
299
  # ============================================================
300
  export CELERY_BROKER_URL="$(
301
  python - <<'PY'
@@ -307,34 +374,60 @@ 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"
@@ -347,19 +440,129 @@ case "${MIGRATION_VALUE}" in
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 "============================================================"
@@ -382,16 +585,24 @@ gunicorn \
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
@@ -401,7 +612,9 @@ for attempt in $(seq 1 120); do
401
  break
402
  ;;
403
  esac
 
404
  sleep 1
 
405
  done
406
 
407
  if [ "${API_READY}" != "1" ]; then
@@ -410,7 +623,7 @@ if [ "${API_READY}" != "1" ]; then
410
  fi
411
 
412
  # ============================================================
413
- # Plugin Daemon
414
  # ============================================================
415
  echo ""
416
  echo "============================================================"
@@ -419,6 +632,7 @@ 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}"
@@ -448,26 +662,34 @@ echo "============================================================"
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()
@@ -476,12 +698,15 @@ except Exception:
476
  sys.exit(1)
477
  PY
478
  then
 
479
  PLUGIN_READY=1
480
  echo "βœ… Plugin Daemon ready"
481
  break
 
482
  fi
483
 
484
  sleep 1
 
485
  done
486
 
487
  if [ "${PLUGIN_READY}" != "1" ]; then
@@ -489,7 +714,7 @@ if [ "${PLUGIN_READY}" != "1" ]; then
489
  fi
490
 
491
  # ============================================================
492
- # Celery worker
493
  # ============================================================
494
  echo ""
495
  echo "============================================================"
@@ -501,7 +726,9 @@ echo "============================================================"
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=$!
@@ -509,14 +736,13 @@ WORKER_PID=$!
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 "============================================================"
@@ -524,50 +750,67 @@ 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
@@ -576,7 +819,7 @@ if [ "${WEB_READY}" != "1" ]; then
576
  fi
577
 
578
  # ============================================================
579
- # Nginx
580
  # ============================================================
581
  echo ""
582
  echo "============================================================"
@@ -589,10 +832,14 @@ if ! nginx -t; then
589
  fi
590
 
591
  nginx
 
592
  sleep 2
593
 
594
  # ============================================================
595
- # Low-transfer State Keeper
 
 
 
596
  # ============================================================
597
  echo ""
598
  echo "============================================================"
@@ -600,6 +847,7 @@ 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
 
@@ -612,24 +860,40 @@ else
612
  exit 1
613
  fi
614
 
 
 
 
615
  echo ""
616
  echo "============================================================"
617
  echo "βœ… DIFY 1.17.0 STARTUP COMPLETE"
618
  echo "============================================================"
 
619
  echo "Public: ${PUBLIC_ORIGIN}"
620
  echo "Redis: ${REDIS_MODE}"
621
  echo "API: READY"
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
@@ -641,16 +905,29 @@ cleanup() {
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
@@ -667,18 +944,23 @@ while true; do
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
 
1
  #!/bin/bash
2
  # ============================================================
3
  # Dify 1.17.0 All-in-One for Hugging Face Spaces
4
+ # FIX 2
5
+ #
6
+ # Fixes:
7
+ # 1. Web 1.17 runs with official /app/entrypoint.sh and /app/targets.
8
+ # 2. Creates dify_plugin automatically if the fresh Neon project
9
+ # does not already have it.
10
+ # 3. When Redis TLS uses CERT_REQUIRED, provides the system CA bundle
11
+ # required by Plugin Daemon 0.6.10.
12
+ # 4. Keeps the Low-Transfer State Keeper.
13
  # ============================================================
14
 
15
  set -E -o pipefail
16
 
17
  echo ""
18
  echo "============================================================"
19
+ echo "πŸš€ Dify 1.17.0 - HF All-in-One FIX 2"
20
  echo "============================================================"
21
 
22
  HF_HOST="${SPACE_HOST:-pommsn-dify.hf.space}"
 
27
  echo "🌍 PUBLIC_ORIGIN = ${PUBLIC_ORIGIN}"
28
 
29
  # ============================================================
30
+ # 1. Core runtime
31
  # ============================================================
32
  export LANG="${LANG:-en_US.UTF-8}"
33
  export LC_ALL="${LC_ALL:-en_US.UTF-8}"
 
43
  export DIFY_PORT="5001"
44
 
45
  # ============================================================
46
+ # 2. Storage
47
  # ============================================================
48
  export STORAGE_TYPE="local"
49
  export STORAGE_LOCAL_PATH="/app/api/storage"
 
65
  "${PLUGIN_STORAGE_LOCAL_ROOT}/${PLUGIN_MEDIA_CACHE_PATH}"
66
 
67
  # ============================================================
68
+ # 3. URLs
69
  # ============================================================
70
  export CONSOLE_API_URL="${PUBLIC_ORIGIN}"
71
  export CONSOLE_WEB_URL="${PUBLIC_ORIGIN}"
 
84
  export NEXT_PUBLIC_COOKIE_DOMAIN=""
85
 
86
  # ============================================================
87
+ # 4. Web
88
  # ============================================================
89
  export NEXT_PUBLIC_DEPLOY_ENV="${DEPLOY_ENV}"
90
  export NEXT_PUBLIC_EDITION="${EDITION}"
 
95
 
96
  export NEXT_TELEMETRY_DISABLED="${NEXT_TELEMETRY_DISABLED:-1}"
97
  export EXPERIMENTAL_ENABLE_VINEXT="${EXPERIMENTAL_ENABLE_VINEXT:-false}"
98
+
99
  export TEXT_GENERATION_TIMEOUT_MS="${TEXT_GENERATION_TIMEOUT_MS:-60000}"
100
  export CSP_WHITELIST="${CSP_WHITELIST:-}"
101
  export ALLOW_EMBED="${ALLOW_EMBED:-false}"
102
  export ALLOW_UNSAFE_DATA_SCHEME="${ALLOW_UNSAFE_DATA_SCHEME:-false}"
103
 
104
  # ============================================================
105
+ # 5. Plugin Daemon
106
  # ============================================================
107
  export DB_PLUGIN_DATABASE="${DB_PLUGIN_DATABASE:-dify_plugin}"
108
 
 
125
  if [ -z "${PLUGIN_DAEMON_KEY:-}" ]; then
126
  export PLUGIN_DAEMON_KEY="$(
127
  python - <<'PY'
128
+ import hashlib
129
+ import os
130
+
131
  secret = os.environ.get("SECRET_KEY", "dify")
132
  print(hashlib.sha256(("plugin-daemon:" + secret).encode()).hexdigest())
133
  PY
 
137
  if [ -z "${PLUGIN_DIFY_INNER_API_KEY:-}" ]; then
138
  export PLUGIN_DIFY_INNER_API_KEY="$(
139
  python - <<'PY'
140
+ import hashlib
141
+ import os
142
+
143
  secret = os.environ.get("SECRET_KEY", "dify")
144
  print(hashlib.sha256(("plugin-inner-api:" + secret).encode()).hexdigest())
145
  PY
 
149
  export INNER_API_KEY_FOR_PLUGIN="${PLUGIN_DIFY_INNER_API_KEY}"
150
 
151
  # ============================================================
152
+ # 6. Validate required settings
153
  # ============================================================
154
  if [ -z "${SECRET_KEY:-}" ]; then
155
  echo "❌ SECRET_KEY is missing"
 
178
  echo "-----------------------------------------"
179
 
180
  # ============================================================
181
+ # 7. State Keeper restore
182
  # ============================================================
183
  echo ""
184
  echo "============================================================"
 
200
  fi
201
 
202
  # ============================================================
203
+ # 8. Redis
204
  # ============================================================
205
  LOCAL_REDIS_ACTIVE=0
206
  REDIS_MODE="UNKNOWN"
207
 
208
+ # Plugin Daemon 0.6.10 requires an explicit CA bundle when
209
+ # REDIS_SSL_CERT_REQS=CERT_REQUIRED.
210
+ if [ "${REDIS_USE_SSL:-false}" = "true" ] || \
211
+ [ "${REDIS_USE_SSL:-false}" = "TRUE" ] || \
212
+ [ "${REDIS_USE_SSL:-false}" = "1" ]; then
213
+
214
+ if [ "${REDIS_SSL_CERT_REQS:-}" = "CERT_REQUIRED" ] && \
215
+ [ -z "${REDIS_SSL_CA_CERTS:-}" ]; then
216
+
217
+ export REDIS_SSL_CA_CERTS="/etc/ssl/certs/ca-certificates.crt"
218
+ echo "πŸ” Redis CA bundle = ${REDIS_SSL_CA_CERTS}"
219
+ fi
220
+ fi
221
+
222
  test_redis_connection() {
223
  python - <<'PY'
224
+ import os
225
+ import ssl
226
+ import sys
227
+ import redis
228
 
229
  host = os.environ.get("REDIS_HOST", "")
230
  port = int(os.environ.get("REDIS_PORT", "6379"))
231
  username = os.environ.get("REDIS_USERNAME", "") or None
232
  password = os.environ.get("REDIS_PASSWORD", "") or None
233
+ use_ssl = os.environ.get("REDIS_USE_SSL", "false").strip().lower() in (
234
+ "true", "1", "yes", "on"
235
+ )
236
 
237
  if not host:
238
  sys.exit(1)
 
249
 
250
  if use_ssl:
251
  kwargs["ssl"] = True
252
+
253
+ cert_value = os.environ.get(
254
+ "REDIS_SSL_CERT_REQS",
255
+ "CERT_NONE"
256
+ ).upper()
257
+
258
  cert_map = {
259
  "CERT_NONE": ssl.CERT_NONE,
260
  "CERT_OPTIONAL": ssl.CERT_OPTIONAL,
261
  "CERT_REQUIRED": ssl.CERT_REQUIRED,
262
  }
263
+
264
+ kwargs["ssl_cert_reqs"] = cert_map.get(
265
+ cert_value,
266
+ ssl.CERT_NONE
267
+ )
268
+
269
+ ca_certs = os.environ.get("REDIS_SSL_CA_CERTS", "")
270
+ if ca_certs:
271
+ kwargs["ssl_ca_certs"] = ca_certs
272
 
273
  try:
274
  client = redis.Redis(**kwargs)
275
  client.ping()
276
+
277
+ print(
278
+ f"βœ… Redis PING OK: "
279
+ f"{host}:{port} ssl={use_ssl}"
280
+ )
281
+
282
  sys.exit(0)
283
+
284
  except Exception as exc:
285
+ print(
286
+ f"❌ Redis connection failed: "
287
+ f"{type(exc).__name__}: {exc}"
288
+ )
289
+
290
  sys.exit(1)
291
  PY
292
  }
 
299
  EXTERNAL_REDIS_OK=0
300
 
301
  if [ -n "${REDIS_HOST:-}" ]; then
302
+
303
+ for attempt in 1 2 3 4 5
304
+ do
305
  echo "Redis attempt ${attempt}/5"
306
+
307
  if test_redis_connection; then
308
  EXTERNAL_REDIS_OK=1
309
  break
310
  fi
311
+
312
  sleep 3
313
  done
314
+
315
  fi
316
 
317
  if [ "${EXTERNAL_REDIS_OK}" = "1" ]; then
318
+
319
  REDIS_MODE="EXTERNAL"
320
  echo "βœ… Using external Redis"
321
+
322
  else
323
+
324
+ echo ""
325
  echo "⚠️ External Redis unavailable"
326
  echo "⚠️ Starting local Redis fallback"
327
 
 
342
  export REDIS_PASSWORD=""
343
  export REDIS_USE_SSL="false"
344
  export REDIS_SSL_CERT_REQS="CERT_NONE"
345
+ export REDIS_SSL_CA_CERTS=""
346
  export REDIS_DB="0"
347
  export REDIS_USE_SENTINEL="false"
348
  export REDIS_USE_CLUSTERS="false"
349
 
350
  REDIS_MODE="LOCAL_FALLBACK"
351
  LOCAL_REDIS_ACTIVE=1
352
+
353
  sleep 2
354
+
355
  fi
356
 
357
  if ! test_redis_connection; then
 
362
  echo "βœ… Redis mode = ${REDIS_MODE}"
363
 
364
  # ============================================================
365
+ # 9. Celery broker
366
  # ============================================================
367
  export CELERY_BROKER_URL="$(
368
  python - <<'PY'
 
374
  username = os.environ.get("REDIS_USERNAME", "")
375
  password = os.environ.get("REDIS_PASSWORD", "")
376
  redis_db = os.environ.get("REDIS_DB", "0")
377
+
378
+ ssl_enabled = os.environ.get(
379
+ "REDIS_USE_SSL",
380
+ "false"
381
+ ).lower() in ("true", "1", "yes", "on")
382
+
383
+ cert_reqs = os.environ.get(
384
+ "REDIS_SSL_CERT_REQS",
385
+ "CERT_NONE"
386
+ ).upper()
387
 
388
  scheme = "rediss" if ssl_enabled else "redis"
389
 
390
  if username:
391
+ auth = (
392
+ quote(username, safe="")
393
+ + ":"
394
+ + quote(password, safe="")
395
+ + "@"
396
+ )
397
  elif password:
398
  auth = ":" + quote(password, safe="") + "@"
399
  else:
400
  auth = ""
401
 
402
+ query = (
403
+ f"?ssl_cert_reqs={cert_reqs}"
404
+ if ssl_enabled
405
+ else ""
406
+ )
407
+
408
+ print(
409
+ f"{scheme}://{auth}"
410
+ f"{host}:{port}/{redis_db}{query}"
411
+ )
412
  PY
413
  )"
414
 
415
  export CELERY_BACKEND="redis"
416
 
417
  # ============================================================
418
+ # 10. Main DB migration
419
  # ============================================================
420
  cd /app/api
421
 
422
+ MIGRATION_VALUE="$(
423
+ echo "${MIGRATION_ENABLED:-true}" |
424
+ tr '[:upper:]' '[:lower:]'
425
+ )"
426
 
427
  case "${MIGRATION_VALUE}" in
428
+
429
  true|1|yes|on)
430
+
431
  echo ""
432
  echo "============================================================"
433
  echo "πŸ—„οΈ Running Dify 1.17 DB Migration"
 
440
  exit 1
441
  fi
442
  ;;
443
+
444
  *)
445
+
446
  echo "ℹ️ Migration disabled"
447
  ;;
448
+
449
  esac
450
 
451
+ # ============================================================
452
+ # 11. Ensure plugin database exists
453
+ #
454
+ # This makes a fresh Neon project easier to deploy. If dify_plugin
455
+ # already exists, no change is made.
456
+ # ============================================================
457
+ echo ""
458
+ echo "============================================================"
459
+ echo "🧩 Checking Neon plugin database"
460
+ echo "============================================================"
461
+
462
+ if python - <<'PY'
463
+ import os
464
+ import sys
465
+
466
+ import psycopg2
467
+ from psycopg2 import sql
468
+
469
+ host = os.environ["DB_HOST"]
470
+ port = int(os.environ.get("DB_PORT", "5432"))
471
+ user = os.environ["DB_USERNAME"]
472
+ password = os.environ["DB_PASSWORD"]
473
+ main_db = os.environ.get("DB_DATABASE", "neondb")
474
+ plugin_db = os.environ.get(
475
+ "DB_PLUGIN_DATABASE",
476
+ "dify_plugin"
477
+ )
478
+ sslmode = os.environ.get(
479
+ "DB_SSL_MODE",
480
+ "require"
481
+ )
482
+
483
+ try:
484
+ conn = psycopg2.connect(
485
+ host=host,
486
+ port=port,
487
+ user=user,
488
+ password=password,
489
+ dbname=main_db,
490
+ sslmode=sslmode,
491
+ connect_timeout=15,
492
+ )
493
+
494
+ conn.autocommit = True
495
+
496
+ with conn.cursor() as cur:
497
+
498
+ cur.execute(
499
+ "SELECT 1 "
500
+ "FROM pg_database "
501
+ "WHERE datname = %s",
502
+ (plugin_db,),
503
+ )
504
+
505
+ exists = cur.fetchone() is not None
506
+
507
+ if exists:
508
+ print(
509
+ f"βœ… Plugin database exists: "
510
+ f"{plugin_db}"
511
+ )
512
+ else:
513
+ print(
514
+ f"ℹ️ Creating plugin database: "
515
+ f"{plugin_db}"
516
+ )
517
+
518
+ cur.execute(
519
+ sql.SQL(
520
+ "CREATE DATABASE {}"
521
+ ).format(
522
+ sql.Identifier(plugin_db)
523
+ )
524
+ )
525
+
526
+ print(
527
+ f"βœ… Plugin database created: "
528
+ f"{plugin_db}"
529
+ )
530
+
531
+ conn.close()
532
+ sys.exit(0)
533
+
534
+ except Exception as exc:
535
+ print(
536
+ "❌ Plugin database check/create failed: "
537
+ f"{type(exc).__name__}: {exc}"
538
+ )
539
+
540
+ sys.exit(1)
541
+ PY
542
+ then
543
+ echo "βœ… Plugin database ready"
544
+ else
545
+ echo "❌ Plugin database is not ready"
546
+ echo "❌ Create database '${DB_PLUGIN_DATABASE}' in Neon manually"
547
+ exit 1
548
+ fi
549
+
550
+ # ============================================================
551
+ # 12. Fresh-install RSA status
552
+ # ============================================================
553
  if python /app/hf_state_keeper.py has-private-key; then
554
+
555
  echo "βœ… Tenant RSA private key already present/restored"
556
+
557
  else
558
+
559
  echo "ℹ️ No tenant private.pem yet - normal for fresh Dify setup"
560
+ echo "ℹ️ State Keeper will back it up after Dify creates it"
561
+
562
  fi
563
 
564
  # ============================================================
565
+ # 13. Start API
566
  # ============================================================
567
  echo ""
568
  echo "============================================================"
 
585
  API_PID=$!
586
 
587
  API_READY=0
588
+
589
+ for attempt in $(seq 1 120)
590
+ do
591
+
592
  if ! kill -0 "${API_PID}" 2>/dev/null; then
593
  echo "❌ API stopped"
594
  exit 1
595
  fi
596
 
597
  HTTP_CODE="$(
598
+ curl \
599
+ --silent \
600
+ --output /dev/null \
601
+ --write-out "%{http_code}" \
602
+ --connect-timeout 2 \
603
+ --max-time 5 \
604
+ http://127.0.0.1:5001/console/api/setup \
605
+ || true
606
  )"
607
 
608
  case "${HTTP_CODE}" in
 
612
  break
613
  ;;
614
  esac
615
+
616
  sleep 1
617
+
618
  done
619
 
620
  if [ "${API_READY}" != "1" ]; then
 
623
  fi
624
 
625
  # ============================================================
626
+ # 14. Start Plugin Daemon
627
  # ============================================================
628
  echo ""
629
  echo "============================================================"
 
632
 
633
  (
634
  export DB_DATABASE="${DB_PLUGIN_DATABASE}"
635
+
636
  export SERVER_PORT="${PLUGIN_DAEMON_PORT}"
637
  export SERVER_KEY="${PLUGIN_DAEMON_KEY}"
638
  export MAX_PLUGIN_PACKAGE_SIZE="${PLUGIN_MAX_PACKAGE_SIZE}"
 
662
 
663
  export UV_PATH="/usr/local/bin/uv"
664
  export PYTHON_INTERPRETER_PATH="/usr/local/bin/python3.12"
665
+
666
  export PLATFORM="local"
667
  export GIN_MODE="release"
668
  export TIKTOKEN_CACHE_DIR="/app/.tiktoken"
669
 
670
  exec /opt/dify-plugin-daemon/main
671
+
672
  ) &
673
 
674
  PLUGIN_PID=$!
675
 
676
  PLUGIN_READY=0
677
+
678
+ for attempt in $(seq 1 120)
679
+ do
680
+
681
  if ! kill -0 "${PLUGIN_PID}" 2>/dev/null; then
682
  echo "❌ Plugin Daemon stopped"
683
  break
684
  fi
685
 
686
  if python - <<'PY'
687
+ import socket
688
+ import sys
689
+
690
  sock = socket.socket()
691
  sock.settimeout(1)
692
+
693
  try:
694
  sock.connect(("127.0.0.1", 5002))
695
  sock.close()
 
698
  sys.exit(1)
699
  PY
700
  then
701
+
702
  PLUGIN_READY=1
703
  echo "βœ… Plugin Daemon ready"
704
  break
705
+
706
  fi
707
 
708
  sleep 1
709
+
710
  done
711
 
712
  if [ "${PLUGIN_READY}" != "1" ]; then
 
714
  fi
715
 
716
  # ============================================================
717
+ # 15. Celery Worker
718
  # ============================================================
719
  echo ""
720
  echo "============================================================"
 
726
  export MIGRATION_ENABLED="false"
727
  export CELERY_WORKER_AMOUNT="${CELERY_WORKER_AMOUNT:-1}"
728
  export CELERY_AUTO_SCALE="false"
729
+
730
  exec /bin/bash /entrypoint.sh
731
+
732
  ) &
733
 
734
  WORKER_PID=$!
 
736
  sleep 5
737
 
738
  # ============================================================
739
+ # 16. Dify Web 1.17
740
+ #
741
+ # Official web entrypoint has absolute paths:
742
+ # /app/targets/next/web/server.js
743
+ # /app/targets/vinext/server.js
744
  #
745
+ # Therefore Dockerfile keeps the runtime at /app/targets.
 
 
 
 
 
746
  # ============================================================
747
  echo ""
748
  echo "============================================================"
 
750
  echo "============================================================"
751
 
752
  (
753
+ cd /app
754
 
755
  export NODE_ENV="production"
756
  export PORT="3000"
757
  export HOSTNAME="0.0.0.0"
758
+
759
  export CONSOLE_API_URL="${PUBLIC_ORIGIN}"
760
  export SERVER_CONSOLE_API_URL="http://127.0.0.1:5001"
761
  export APP_API_URL="${PUBLIC_ORIGIN}"
762
+
763
  export MARKETPLACE_API_URL="${MARKETPLACE_API_URL}"
764
  export MARKETPLACE_URL="${MARKETPLACE_URL}"
765
+
766
  export NEXT_PUBLIC_COOKIE_DOMAIN=""
767
  export NEXT_TELEMETRY_DISABLED="${NEXT_TELEMETRY_DISABLED:-1}"
768
  export EXPERIMENTAL_ENABLE_VINEXT="${EXPERIMENTAL_ENABLE_VINEXT:-false}"
769
+
770
  export TEXT_GENERATION_TIMEOUT_MS="${TEXT_GENERATION_TIMEOUT_MS:-60000}"
771
  export CSP_WHITELIST="${CSP_WHITELIST:-}"
772
  export ALLOW_EMBED="${ALLOW_EMBED:-false}"
773
  export ALLOW_UNSAFE_DATA_SCHEME="${ALLOW_UNSAFE_DATA_SCHEME:-false}"
774
 
775
+ exec /bin/sh /app/entrypoint.sh
776
+
777
  ) &
778
 
779
  WEB_PID=$!
780
 
781
  WEB_READY=0
782
+
783
+ for attempt in $(seq 1 120)
784
+ do
785
+
786
  if ! kill -0 "${WEB_PID}" 2>/dev/null; then
787
  echo "❌ Web stopped"
788
  exit 1
789
  fi
790
 
791
  HTTP_CODE="$(
792
+ curl \
793
+ --silent \
794
+ --output /dev/null \
795
+ --write-out "%{http_code}" \
796
+ --connect-timeout 2 \
797
+ --max-time 5 \
798
+ http://127.0.0.1:3000/signin \
799
+ || true
800
  )"
801
 
802
  case "${HTTP_CODE}" in
803
+
804
  2*|3*|4*)
805
  WEB_READY=1
806
  echo "βœ… Web ready HTTP ${HTTP_CODE}"
807
  break
808
  ;;
809
+
810
  esac
811
+
812
  sleep 1
813
+
814
  done
815
 
816
  if [ "${WEB_READY}" != "1" ]; then
 
819
  fi
820
 
821
  # ============================================================
822
+ # 17. Nginx
823
  # ============================================================
824
  echo ""
825
  echo "============================================================"
 
832
  fi
833
 
834
  nginx
835
+
836
  sleep 2
837
 
838
  # ============================================================
839
+ # 18. Low-Transfer State Keeper
840
+ #
841
+ # No --force here. This prevents a ~large plugin snapshot upload
842
+ # on every ordinary startup/shutdown.
843
  # ============================================================
844
  echo ""
845
  echo "============================================================"
 
847
  echo "============================================================"
848
 
849
  python /app/hf_state_keeper.py sync-once || true
850
+
851
  python /app/hf_state_keeper.py watch &
852
  STATE_KEEPER_PID=$!
853
 
 
860
  exit 1
861
  fi
862
 
863
+ # ============================================================
864
+ # 19. Summary
865
+ # ============================================================
866
  echo ""
867
  echo "============================================================"
868
  echo "βœ… DIFY 1.17.0 STARTUP COMPLETE"
869
  echo "============================================================"
870
+
871
  echo "Public: ${PUBLIC_ORIGIN}"
872
  echo "Redis: ${REDIS_MODE}"
873
  echo "API: READY"
874
  echo "Web: READY"
875
+
876
+ if [ "${PLUGIN_READY}" = "1" ]; then
877
+ echo "Plugin Daemon: READY"
878
+ else
879
+ echo "Plugin Daemon: NOT READY"
880
+ fi
881
+
882
  echo "State Keeper: RUNNING"
883
+
884
  echo "============================================================"
885
 
886
  python /app/hf_state_keeper.py status || true
887
 
888
+ # ============================================================
889
+ # 20. Cleanup
890
+ # ============================================================
891
  cleanup() {
892
+
893
  echo ""
894
  echo "πŸ›‘ Stopping Dify..."
895
 
896
+ # Best effort; intentionally no --force.
897
  python /app/hf_state_keeper.py sync-once || true
898
 
899
  nginx -s quit 2>/dev/null || true
 
905
  kill "${API_PID}" 2>/dev/null || true
906
 
907
  if [ "${LOCAL_REDIS_ACTIVE}" = "1" ]; then
908
+
909
+ redis-cli \
910
+ -h 127.0.0.1 \
911
+ -p 6379 \
912
+ shutdown \
913
+ nosave \
914
+ >/dev/null 2>&1 || true
915
+
916
  fi
917
+
918
  }
919
 
920
  trap cleanup EXIT TERM INT
921
 
922
+ # ============================================================
923
+ # 21. Keep Space alive
924
+ # ============================================================
925
  PLUGIN_WARNING_SHOWN=0
926
  WORKER_WARNING_SHOWN=0
927
 
928
+ while true
929
+ do
930
+
931
  if ! kill -0 "${API_PID}" 2>/dev/null; then
932
  echo "❌ API stopped"
933
  exit 1
 
944
  fi
945
 
946
  if ! kill -0 "${PLUGIN_PID}" 2>/dev/null; then
947
+
948
  if [ "${PLUGIN_WARNING_SHOWN}" = "0" ]; then
949
  echo "⚠️ Plugin Daemon stopped"
950
  PLUGIN_WARNING_SHOWN=1
951
  fi
952
+
953
  fi
954
 
955
  if ! kill -0 "${WORKER_PID}" 2>/dev/null; then
956
+
957
  if [ "${WORKER_WARNING_SHOWN}" = "0" ]; then
958
  echo "⚠️ Worker stopped"
959
  WORKER_WARNING_SHOWN=1
960
  fi
961
+
962
  fi
963
 
964
  sleep 10
965
+
966
  done