fix: fix hardcode /home/dify (#39767)

This commit is contained in:
wangxiaolei 2026-07-29 20:27:38 +08:00 committed by GitHub
parent 0441d27cac
commit 080a1d42f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 7 deletions

View File

@ -80,6 +80,10 @@ Binding backend. A standalone Local server normally uses:
DIFY_AGENT_RUNTIME_BACKEND=local
DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT=http://127.0.0.1:5004
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=
# Optional when shellctl runs directly on a host without /home/dify:
# DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT=/tmp/dify-agent/materialized-homes
# DIFY_AGENT_LOCAL_SANDBOX_WORKSPACE_ROOT=/tmp/dify-agent/workspaces
# DIFY_AGENT_LOCAL_SANDBOX_HOME_SNAPSHOT_ROOT=/tmp/dify-agent/home-snapshots
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES=52428800
```

View File

@ -42,6 +42,9 @@ also reads `.env` and `dify-agent/.env` when present.
| `DIFY_AGENT_RUNTIME_BACKEND` | `local` | Selects one coherent `local`, `enterprise`, or `e2b` Home Snapshot + Execution Binding backend profile. |
| `DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT` | empty | Local shellctl data-plane URL. With the default Local selection, leaving it empty disables `dify.runtime` and resource endpoints. |
| `DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN` | empty | Optional bearer token sent to Local shellctl. |
| `DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT` | `/home/dify/.dify-agent-materialized-homes` | Root directory, on the Local shellctl filesystem, for per-Binding materialized Homes. |
| `DIFY_AGENT_LOCAL_SANDBOX_WORKSPACE_ROOT` | `/home/dify/.dify-agent-workspaces` | Root directory, on the Local shellctl filesystem, for mutable Workspaces. |
| `DIFY_AGENT_LOCAL_SANDBOX_HOME_SNAPSHOT_ROOT` | `/home/dify/.dify-agent-home-snapshots` | Root directory, on the Local shellctl filesystem, for immutable Home Snapshots. |
| `DIFY_AGENT_ENTERPRISE_SANDBOX_GATEWAY_ENDPOINT` | empty | Enterprise Gateway endpoint required by configuration. Default-Home Bindings are supported; immutable Home Snapshot operations remain unsupported. |
| `DIFY_AGENT_ENTERPRISE_SANDBOX_GATEWAY_AUTH_TOKEN` | empty | Optional `X-Inner-Api-Key` sent to the Enterprise Gateway. |
| `DIFY_AGENT_ENTERPRISE_SANDBOX_GATEWAY_TIMEOUT` | `30` | Enterprise control-plane timeout in seconds. |
@ -78,6 +81,10 @@ DIFY_AGENT_INNER_API_KEY=replace-with-dify-inner-api-key-for-plugin
DIFY_AGENT_RUNTIME_BACKEND=local
DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT=http://127.0.0.1:5004
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=replace-with-shellctl-token
# Set these when shellctl runs directly on a host that does not have /home/dify.
DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT=/tmp/dify-agent/materialized-homes
DIFY_AGENT_LOCAL_SANDBOX_WORKSPACE_ROOT=/tmp/dify-agent/workspaces
DIFY_AGENT_LOCAL_SANDBOX_HOME_SNAPSHOT_ROOT=/tmp/dify-agent/home-snapshots
DIFY_AGENT_SANDBOX_FILE_UPLOAD_MAX_BYTES=52428800
DIFY_AGENT_STUB_API_BASE_URL=https://agent.example.com/agent-stub
# This is security-sensitive: it derives the JWE encryption key for Agent Stub bearer tokens.
@ -90,7 +97,7 @@ DIFY_AGENT_SERVER_SECRET_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY
accepted only as legacy aliases for the two Local settings. New deployments
must use `DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT` and
`DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN`. There is no compatibility setting for
the removed shell-provider selector or Shell-owned Home root.
the removed shell-provider selector.
The example above is for a standalone Dify Agent process, where the byte limit
can be set directly. In a Docker deployment, set `PLUGIN_MAX_FILE_SIZE` in

View File

@ -71,6 +71,10 @@ Equivalent standalone environment settings are:
DIFY_AGENT_RUNTIME_BACKEND=local
DIFY_AGENT_LOCAL_SANDBOX_ENDPOINT=http://127.0.0.1:5004
DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN=replace-with-shellctl-token
# Optional when shellctl runs directly on a host without /home/dify:
# DIFY_AGENT_LOCAL_SANDBOX_MATERIALIZED_HOME_ROOT=/tmp/dify-agent/materialized-homes
# DIFY_AGENT_LOCAL_SANDBOX_WORKSPACE_ROOT=/tmp/dify-agent/workspaces
# DIFY_AGENT_LOCAL_SANDBOX_HOME_SNAPSHOT_ROOT=/tmp/dify-agent/home-snapshots
```
The auth token may be empty when shellctl authentication is disabled. E2B uses

View File

@ -2,6 +2,7 @@
from __future__ import annotations
import posixpath
from typing import ClassVar, Literal, Self
from urllib.parse import urlparse
@ -19,6 +20,9 @@ from dify_agent.runtime_backend.local import LocalExecutionBindingBackend, Local
from dify_agent.runtime_backend.protocols import RuntimeBackendProfile
DEFAULT_E2B_TEMPLATE = "difys-default-team/dify-agent-local-sandbox"
DEFAULT_LOCAL_MATERIALIZED_HOME_ROOT = "/home/dify/.dify-agent-materialized-homes"
DEFAULT_LOCAL_WORKSPACE_ROOT = "/home/dify/.dify-agent-workspaces"
DEFAULT_LOCAL_HOME_SNAPSHOT_ROOT = "/home/dify/.dify-agent-home-snapshots"
class RuntimeBackendSettings(BaseSettings):
@ -40,6 +44,9 @@ class RuntimeBackendSettings(BaseSettings):
"DIFY_AGENT_SHELLCTL_AUTH_TOKEN",
),
)
local_sandbox_materialized_home_root: str = DEFAULT_LOCAL_MATERIALIZED_HOME_ROOT
local_sandbox_workspace_root: str = DEFAULT_LOCAL_WORKSPACE_ROOT
local_sandbox_home_snapshot_root: str = DEFAULT_LOCAL_HOME_SNAPSHOT_ROOT
enterprise_sandbox_gateway_endpoint: str | None = None
enterprise_sandbox_gateway_auth_token: str | None = None
@ -70,6 +77,18 @@ class RuntimeBackendSettings(BaseSettings):
if not self.local_sandbox_endpoint or not self.local_sandbox_endpoint.strip():
raise ValueError("local_sandbox_endpoint is required for the local runtime backend")
_validate_http_url(self.local_sandbox_endpoint, field_name="local_sandbox_endpoint")
_validate_absolute_posix_path(
self.local_sandbox_materialized_home_root,
field_name="local_sandbox_materialized_home_root",
)
_validate_absolute_posix_path(
self.local_sandbox_workspace_root,
field_name="local_sandbox_workspace_root",
)
_validate_absolute_posix_path(
self.local_sandbox_home_snapshot_root,
field_name="local_sandbox_home_snapshot_root",
)
case "enterprise":
endpoint = self.enterprise_sandbox_gateway_endpoint
if not endpoint or not endpoint.strip():
@ -92,8 +111,18 @@ def create_runtime_backend_profile(settings: RuntimeBackendSettings) -> RuntimeB
endpoint = settings.local_sandbox_endpoint or ""
token = settings.local_sandbox_auth_token or ""
return RuntimeBackendProfile(
home_snapshots=LocalHomeSnapshotBackend(endpoint=endpoint, auth_token=token),
execution_bindings=LocalExecutionBindingBackend(endpoint=endpoint, auth_token=token),
home_snapshots=LocalHomeSnapshotBackend(
endpoint=endpoint,
auth_token=token,
snapshot_root=settings.local_sandbox_home_snapshot_root,
),
execution_bindings=LocalExecutionBindingBackend(
endpoint=endpoint,
auth_token=token,
materialized_home_root=settings.local_sandbox_materialized_home_root,
workspace_root=settings.local_sandbox_workspace_root,
snapshot_root=settings.local_sandbox_home_snapshot_root,
),
)
case "enterprise":
endpoint = settings.enterprise_sandbox_gateway_endpoint or ""
@ -129,4 +158,16 @@ def _validate_http_url(value: str, *, field_name: str) -> None:
raise ValueError(f"{field_name} must be a valid http(s) URL")
__all__ = ["DEFAULT_E2B_TEMPLATE", "RuntimeBackendSettings", "create_runtime_backend_profile"]
def _validate_absolute_posix_path(value: str, *, field_name: str) -> None:
if not value.strip() or not posixpath.isabs(value):
raise ValueError(f"{field_name} must be an absolute POSIX path")
__all__ = [
"DEFAULT_E2B_TEMPLATE",
"DEFAULT_LOCAL_HOME_SNAPSHOT_ROOT",
"DEFAULT_LOCAL_MATERIALIZED_HOME_ROOT",
"DEFAULT_LOCAL_WORKSPACE_ROOT",
"RuntimeBackendSettings",
"create_runtime_backend_profile",
]

View File

@ -25,7 +25,13 @@ from dify_agent.agent_stub.server.grpc_bind import normalize_agent_stub_grpc_bin
from dify_agent.agent_stub.server.tokens.agent_stub import AgentStubTokenCodec, decode_server_secret_key
from dify_agent.runtime_backend import RuntimeBackendProfile
from dify_agent.runtime_backend.e2b import E2B_MAX_ACTIVE_TIMEOUT_SECONDS
from dify_agent.runtime_backend.profile import RuntimeBackendSettings, create_runtime_backend_profile
from dify_agent.runtime_backend.profile import (
DEFAULT_LOCAL_HOME_SNAPSHOT_ROOT,
DEFAULT_LOCAL_MATERIALIZED_HOME_ROOT,
DEFAULT_LOCAL_WORKSPACE_ROOT,
RuntimeBackendSettings,
create_runtime_backend_profile,
)
DEFAULT_RUN_RETENTION_SECONDS = 3 * 24 * 60 * 60
@ -50,6 +56,9 @@ class ServerSettings(BaseSettings):
default=None,
validation_alias=AliasChoices("DIFY_AGENT_LOCAL_SANDBOX_AUTH_TOKEN", "DIFY_AGENT_SHELLCTL_AUTH_TOKEN"),
)
local_sandbox_materialized_home_root: str = DEFAULT_LOCAL_MATERIALIZED_HOME_ROOT
local_sandbox_workspace_root: str = DEFAULT_LOCAL_WORKSPACE_ROOT
local_sandbox_home_snapshot_root: str = DEFAULT_LOCAL_HOME_SNAPSHOT_ROOT
enterprise_sandbox_gateway_endpoint: str | None = None
enterprise_sandbox_gateway_auth_token: str | None = None
enterprise_sandbox_gateway_timeout: float = Field(default=30.0, gt=0)
@ -178,6 +187,9 @@ class ServerSettings(BaseSettings):
runtime_backend=self.runtime_backend,
local_sandbox_endpoint=self.local_sandbox_endpoint,
local_sandbox_auth_token=self.local_sandbox_auth_token,
local_sandbox_materialized_home_root=self.local_sandbox_materialized_home_root,
local_sandbox_workspace_root=self.local_sandbox_workspace_root,
local_sandbox_home_snapshot_root=self.local_sandbox_home_snapshot_root,
enterprise_sandbox_gateway_endpoint=self.enterprise_sandbox_gateway_endpoint,
enterprise_sandbox_gateway_auth_token=self.enterprise_sandbox_gateway_auth_token,
enterprise_sandbox_gateway_timeout=self.enterprise_sandbox_gateway_timeout,

View File

@ -4,7 +4,12 @@ import pytest
from pydantic import ValidationError
from dify_agent.runtime_backend.e2b import E2B_MAX_ACTIVE_TIMEOUT_SECONDS
from dify_agent.runtime_backend.profile import DEFAULT_E2B_TEMPLATE, RuntimeBackendSettings
from dify_agent.runtime_backend.local import LocalExecutionBindingBackend, LocalHomeSnapshotBackend
from dify_agent.runtime_backend.profile import (
DEFAULT_E2B_TEMPLATE,
RuntimeBackendSettings,
create_runtime_backend_profile,
)
def test_e2b_backend_uses_prepared_dify_template_by_default() -> None:
@ -32,3 +37,31 @@ def test_e2b_backend_rejects_active_timeout_above_platform_limit() -> None:
def test_local_backend_requires_shellctl_endpoint() -> None:
with pytest.raises(ValidationError, match="local_sandbox_endpoint"):
_ = RuntimeBackendSettings(runtime_backend="local")
def test_local_backend_passes_configured_roots_to_drivers() -> None:
settings = RuntimeBackendSettings(
runtime_backend="local",
local_sandbox_endpoint="http://shellctl.example",
local_sandbox_materialized_home_root="/tmp/dify/homes",
local_sandbox_workspace_root="/tmp/dify/workspaces",
local_sandbox_home_snapshot_root="/tmp/dify/snapshots",
)
profile = create_runtime_backend_profile(settings)
assert isinstance(profile.execution_bindings, LocalExecutionBindingBackend)
assert isinstance(profile.home_snapshots, LocalHomeSnapshotBackend)
assert profile.execution_bindings.materialized_home_root == "/tmp/dify/homes"
assert profile.execution_bindings.workspace_root == "/tmp/dify/workspaces"
assert profile.execution_bindings.snapshot_root == "/tmp/dify/snapshots"
assert profile.home_snapshots.snapshot_root == "/tmp/dify/snapshots"
def test_local_backend_rejects_relative_roots() -> None:
with pytest.raises(ValidationError, match="absolute POSIX path"):
_ = RuntimeBackendSettings(
runtime_backend="local",
local_sandbox_endpoint="http://shellctl.example",
local_sandbox_workspace_root="relative/workspaces",
)

View File

@ -14,7 +14,7 @@ from dify_agent.agent_stub.server.tokens.agent_stub import AgentStubTokenCodec
from dify_agent.server.settings import ServerSettings
from dify_agent.runtime_backend.e2b import E2BExecutionBindingBackend
from dify_agent.runtime_backend.enterprise import EnterpriseExecutionBindingBackend
from dify_agent.runtime_backend.local import LocalExecutionBindingBackend
from dify_agent.runtime_backend.local import LocalExecutionBindingBackend, LocalHomeSnapshotBackend
def _base64url_secret(value: bytes) -> str:
@ -260,14 +260,22 @@ def test_build_runtime_backend_profile_returns_local_drivers_when_configured() -
runtime_backend="local",
local_sandbox_endpoint="http://shellctl.example",
local_sandbox_auth_token="shell-secret",
local_sandbox_materialized_home_root="/tmp/dify/homes",
local_sandbox_workspace_root="/tmp/dify/workspaces",
local_sandbox_home_snapshot_root="/tmp/dify/snapshots",
)
profile = settings.build_runtime_backend_profile()
assert profile is not None
assert isinstance(profile.execution_bindings, LocalExecutionBindingBackend)
assert isinstance(profile.home_snapshots, LocalHomeSnapshotBackend)
assert profile.execution_bindings.endpoint == "http://shellctl.example"
assert profile.execution_bindings.auth_token == "shell-secret"
assert profile.execution_bindings.materialized_home_root == "/tmp/dify/homes"
assert profile.execution_bindings.workspace_root == "/tmp/dify/workspaces"
assert profile.execution_bindings.snapshot_root == "/tmp/dify/snapshots"
assert profile.home_snapshots.snapshot_root == "/tmp/dify/snapshots"
def test_build_runtime_backend_profile_returns_enterprise_drivers_when_selected() -> None: