feat: per-site SHIFT path with video search, fix agent token injection, accept cpu_temp
- deploy_agent.sh: agent v3.3-shift — use dashboard-configured SHIFT path for file fetch/search (SR/SHIFT sites have no TAKELEAP folder), date fast-path; fix installer discarding the injected agent token (sentinel was being rewritten by the server's placeholder replace, agents ended up tokenless) - central_api_prototype.py: replace only the token assignment when serving the installer; add cpu_temp to TelemetryPayload (agents already send it, it was silently dropped); new endpoints set-shift-path, request-search, search-results; heartbeat response now carries shift_path + pending search - commands.json: restore last_reboot/dummy, add update_diag diagnostic - CLAUDE.md: document deployment layout, procedures, and gotchas
This commit is contained in:
@@ -325,6 +325,7 @@ class TelemetryPayload(BaseModel):
|
||||
memory_free_gb: float
|
||||
disks: List[Dict[str, Any]]
|
||||
gpus: List[Dict[str, Any]]
|
||||
cpu_temp: Optional[float] = None
|
||||
agent_version: Optional[str] = None
|
||||
speed_upload_mbps: Optional[float] = None
|
||||
speed_download_mbps: Optional[float] = None
|
||||
@@ -335,6 +336,12 @@ class CommandResultPayload(BaseModel):
|
||||
stdout: str
|
||||
stderr: str
|
||||
|
||||
class SearchResultsPayload(BaseModel):
|
||||
query: str
|
||||
results: List[Dict[str, Any]]
|
||||
searched_path: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
|
||||
class LoginPayload(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
@@ -735,6 +742,57 @@ async def request_file(client_id: str, filename: str, current_user: str = Depend
|
||||
append_to_logs("file_requested", client_id, {"filename": safe_name})
|
||||
return {"status": "success", "message": f"File '{safe_name}' requested from {client_id}"}
|
||||
|
||||
@app.post("/api/set-shift-path")
|
||||
async def set_shift_path(client_id: str, shift_path: str = "", current_user: str = Depends(get_current_user)):
|
||||
"""
|
||||
Store where this node keeps its SHIFT folder (varies per site:
|
||||
/mnt/<disk-uuid>/SR/SHIFT or /mnt/<disk-uuid>/TAKELEAP/SHIFT).
|
||||
Delivered to the agent in every heartbeat response.
|
||||
"""
|
||||
path = shift_path.strip()
|
||||
result = clients_collection.update_one({"_id": client_id}, {"$set": {"shift_path": path}})
|
||||
if result.matched_count == 0:
|
||||
raise HTTPException(status_code=404, detail="Unknown client_id")
|
||||
append_to_logs("shift_path_set", client_id, {"shift_path": path})
|
||||
return {"status": "success", "shift_path": path}
|
||||
|
||||
@app.post("/api/request-search")
|
||||
async def request_search(client_id: str, query: str, current_user: str = Depends(get_current_user)):
|
||||
"""
|
||||
Queue a filename search in the node's SHIFT folder. The agent picks the
|
||||
query up on its next heartbeat and posts matches to /api/search-results.
|
||||
"""
|
||||
q = query.strip()
|
||||
if not q:
|
||||
raise HTTPException(status_code=400, detail="Empty search query")
|
||||
now_str = datetime.now().isoformat()
|
||||
result = clients_collection.update_one(
|
||||
{"_id": client_id},
|
||||
{"$set": {"pending_search": q,
|
||||
"file_search": {"query": q, "status": "searching", "results": [], "requested_at": now_str}}}
|
||||
)
|
||||
if result.matched_count == 0:
|
||||
raise HTTPException(status_code=404, detail="Unknown client_id")
|
||||
append_to_logs("search_requested", client_id, {"query": q})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.post("/api/search-results")
|
||||
async def receive_search_results(client_id: str, payload: SearchResultsPayload, token_valid: bool = Depends(require_agent_token)):
|
||||
now_str = datetime.now().isoformat()
|
||||
clients_collection.update_one(
|
||||
{"_id": client_id},
|
||||
{"$set": {"file_search": {
|
||||
"query": payload.query,
|
||||
"status": "error" if payload.error else "done",
|
||||
"results": payload.results[:200],
|
||||
"searched_path": payload.searched_path,
|
||||
"error": payload.error,
|
||||
"completed_at": now_str,
|
||||
}}}
|
||||
)
|
||||
append_to_logs("search_results", client_id, {"query": payload.query, "count": len(payload.results), "error": payload.error})
|
||||
return {"status": "success"}
|
||||
|
||||
@app.get("/api/get-file-request")
|
||||
async def get_file_request(client_id: str, token_valid: bool = Depends(require_agent_token)):
|
||||
if not client_id or not client_id.strip():
|
||||
@@ -907,7 +965,14 @@ async def get_agent_installer(x_agent_token: str = Header(None), token: Optional
|
||||
# the live agent token so a freshly downloaded installer bakes it into the agent.
|
||||
with open(path, "rb") as f:
|
||||
content = f.read().replace(b"\r\n", b"\n")
|
||||
content = content.replace(b"__AGENT_TOKEN__", AGENT_TOKEN.encode("utf-8"))
|
||||
# Replace only the assignment, not the "was I injected?" sentinel comparison
|
||||
# a few lines below it — a blanket replace turns that check into an
|
||||
# always-true self-comparison and the installer discards the injected token.
|
||||
content = content.replace(
|
||||
b'AGENT_TOKEN="__AGENT_TOKEN__"',
|
||||
b'AGENT_TOKEN="' + AGENT_TOKEN.encode("utf-8") + b'"',
|
||||
1,
|
||||
)
|
||||
return Response(
|
||||
content=content,
|
||||
media_type="text/x-shellscript",
|
||||
@@ -949,20 +1014,26 @@ async def receive_heartbeat(client_id: str, payload: TelemetryPayload,
|
||||
)
|
||||
doc = clients_collection.find_one_and_update(
|
||||
{"_id": client_id},
|
||||
{"$set": {"pending_command": "none", "pending_file_request": "none"}},
|
||||
{"$set": {"pending_command": "none", "pending_file_request": "none", "pending_search": "none"}},
|
||||
return_document=False
|
||||
)
|
||||
cmd = "none"
|
||||
fname = "none"
|
||||
search = "none"
|
||||
shift_path = None
|
||||
if doc:
|
||||
cmd = doc.get("pending_command", "none")
|
||||
fname = doc.get("pending_file_request", "none")
|
||||
|
||||
search = doc.get("pending_search", "none") or "none"
|
||||
shift_path = doc.get("shift_path") or None
|
||||
|
||||
if cmd != "none":
|
||||
append_to_logs("command_polled", client_id, {"command": cmd})
|
||||
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"command": cmd,
|
||||
"filename": fname
|
||||
"filename": fname,
|
||||
"search": search,
|
||||
"shift_path": shift_path
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user