Claude Code commited on
Commit
1405c4c
·
1 Parent(s): a86d37e

Claude Code: Fix permission denied errors - use /data for persistent storage

Browse files

- Change WORKDIR to /data with proper permissions (chmod 777)
- Use absolute path /data/cain_status.json directly (no dynamic resolution)
- Create /data/.openclaw directory with write permissions
- Update static file paths to use absolute /data/frontend

Co-Authored-By: Claude Code

Files changed (3) hide show
  1. Dockerfile +13 -10
  2. app.py +2 -2
  3. error_handlers.py +33 -27
Dockerfile CHANGED
@@ -6,21 +6,24 @@ LABEL space_id="tao-shen/HuggingClaw-Cain"
6
  LABEL sdk="docker"
7
  LABEL port="7860"
8
 
9
- WORKDIR /app
 
 
10
 
11
  # Copy requirements and install (layer cache optimization)
12
- COPY requirements.txt .
13
- RUN pip install --no-cache-dir -r requirements.txt
14
 
15
  # Copy all application files - combined layer for faster builds
16
- COPY app.py error_handlers.py entrypoint.sh openclaw.json /app/
17
- COPY openclaw/ /app/openclaw/
18
- COPY static/ /app/static/
19
- COPY frontend/ /app/frontend/
20
 
21
- # Create necessary directories
22
- RUN mkdir -p /app/logs && \
23
- chmod +x /app/entrypoint.sh
 
24
 
25
  ENV PORT=7860
26
  EXPOSE 7860
 
6
  LABEL sdk="docker"
7
  LABEL port="7860"
8
 
9
+ # Create /data directory for persistent storage
10
+ RUN mkdir -p /data && chmod 777 /data
11
+ WORKDIR /data
12
 
13
  # Copy requirements and install (layer cache optimization)
14
+ COPY requirements.txt /tmp/
15
+ RUN pip install --no-cache-dir -r /tmp/requirements.txt
16
 
17
  # Copy all application files - combined layer for faster builds
18
+ COPY app.py error_handlers.py entrypoint.sh openclaw.json /data/
19
+ COPY openclaw/ /data/openclaw/
20
+ COPY static/ /data/static/
21
+ COPY frontend/ /data/frontend/
22
 
23
+ # Create necessary directories with proper permissions
24
+ RUN mkdir -p /data/logs /data/.openclaw && \
25
+ chmod +x /data/entrypoint.sh && \
26
+ chmod 777 /data/logs /data/.openclaw
27
 
28
  ENV PORT=7860
29
  EXPOSE 7860
app.py CHANGED
@@ -34,8 +34,8 @@ async def lifespan(app: FastAPI):
34
 
35
  app = FastAPI(title="HuggingClaw - Cain", version="0.0.1", lifespan=lifespan)
36
 
37
- # Mount frontend folder for static assets (pixel art, fonts, HTML)
38
- app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")
39
 
40
  @app.get("/")
41
  async def root():
 
34
 
35
  app = FastAPI(title="HuggingClaw - Cain", version="0.0.1", lifespan=lifespan)
36
 
37
+ # Mount frontend folder for static assets (pixel art, fonts, HTML) - absolute path
38
+ app.mount("/frontend", StaticFiles(directory="/data/frontend"), name="frontend")
39
 
40
  @app.get("/")
41
  async def root():
error_handlers.py CHANGED
@@ -58,33 +58,11 @@ async def generic_exception_handler(request: Request, exc: Exception) -> JSONRes
58
 
59
 
60
  # Configuration path - cain_status.json location
61
- # Uses dynamic path resolution that works both locally and in Docker container
62
- # Tries multiple strategies to find the status file
63
- def _find_status_file() -> Path:
64
- """Dynamically locate cain_status.json using multiple fallback strategies."""
65
- # Strategy 1: Use environment variable if set (highest priority)
66
- env_path = os.environ.get("CAIN_STATUS_FILE")
67
- if env_path:
68
- return Path(env_path)
69
-
70
- # Strategy 2: Relative to this file's parent directory
71
- # In workspace: error_handlers.py -> openclaw/.openclaw/agents/
72
- # In Docker: /app/error_handlers.py -> /app/openclaw/.openclaw/agents/
73
- base_dir = Path(__file__).parent.resolve()
74
- relative_path = base_dir / "openclaw" / ".openclaw" / "agents" / "cain_status.json"
75
- if relative_path.exists():
76
- return relative_path
77
-
78
- # Strategy 3: Check if we're in the openclaw directory already
79
- # Handles cases where error_handlers.py might be in openclaw/
80
- agents_dir = base_dir / ".openclaw" / "agents" / "cain_status.json"
81
- if agents_dir.exists():
82
- return agents_dir
83
-
84
- # Strategy 4: Fall back to the expected path (may not exist, but that's OK)
85
- return relative_path
86
-
87
- STATUS_FILE = _find_status_file()
88
 
89
 
90
  def handle_status_file_read() -> Dict[str, Any]:
@@ -178,3 +156,31 @@ async def handle_websocket_send(websocket, status_data: dict) -> bool:
178
  return True
179
  except (ConnectionError, RuntimeError, Exception):
180
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
 
60
  # Configuration path - cain_status.json location
61
+ # Absolute path to persistent storage directory
62
+ STATUS_FILE = Path("/data/cain_status.json")
63
+
64
+ # Ensure parent directory exists with proper permissions
65
+ STATUS_FILE.parent.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
 
68
  def handle_status_file_read() -> Dict[str, Any]:
 
156
  return True
157
  except (ConnectionError, RuntimeError, Exception):
158
  return False
159
+
160
+
161
+ def write_cain_status(status_data: Dict[str, Any]) -> bool:
162
+ """
163
+ Write Cain's status to the status file.
164
+
165
+ Args:
166
+ status_data: Dictionary with status information to write.
167
+
168
+ Returns:
169
+ True if write succeeded, False otherwise.
170
+ """
171
+ try:
172
+ # Ensure directory exists
173
+ STATUS_FILE.parent.mkdir(parents=True, exist_ok=True)
174
+
175
+ # Add timestamp if not present
176
+ if "last_updated" not in status_data:
177
+ status_data["last_updated"] = datetime.utcnow().isoformat() + "+00:00"
178
+
179
+ # Write to file
180
+ with open(str(STATUS_FILE), "w") as f:
181
+ json.dump(status_data, f, indent=2)
182
+
183
+ return True
184
+ except (PermissionError, OSError, json.JSONDecodeError) as e:
185
+ print(f"Error writing status file: {e}")
186
+ return False