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:
112
deploy_agent.sh
112
deploy_agent.sh
@@ -40,7 +40,9 @@ if [ -z "$CENTRAL_URL" ]; then CENTRAL_URL="http://rmm-backend.seekright.com"; f
|
||||
AGENT_TOKEN="__AGENT_TOKEN__"
|
||||
if [ -n "$ARG_TOKEN" ]; then
|
||||
AGENT_TOKEN="$ARG_TOKEN"
|
||||
elif [ "$AGENT_TOKEN" = "__AGENT_TOKEN__" ]; then
|
||||
elif [ "$AGENT_TOKEN" = "__AGENT_""TOKEN__" ]; then
|
||||
# (placeholder split in two so the server's token injection can't rewrite this
|
||||
# comparison — it must only replace the assignment above)
|
||||
if [ -f "$EXISTING_PY" ]; then
|
||||
AGENT_TOKEN="$(sed -n 's/^AGENT_TOKEN = "\(.*\)"/\1/p' "$EXISTING_PY" | head -1)"
|
||||
else
|
||||
@@ -99,7 +101,7 @@ def get_cpu_temp():
|
||||
pass
|
||||
return None
|
||||
|
||||
AGENT_VERSION = "3.2-auth"
|
||||
AGENT_VERSION = "3.3-shift"
|
||||
|
||||
CLIENT_ID = "TEMPLATE_CLIENT_ID"
|
||||
CENTRAL_BASE = "TEMPLATE_CENTRAL_URL"
|
||||
@@ -123,6 +125,8 @@ CENTRAL_FILE_STATUS_URL = f"{CENTRAL_BASE}/api/file-transfer-status?client_id={C
|
||||
CACHED_COMMANDS = {}
|
||||
command_in_progress = False
|
||||
file_transfer_in_progress = False
|
||||
SHIFT_PATH = None # per-site SHIFT folder, configured on the dashboard, delivered via heartbeat
|
||||
search_in_progress = False
|
||||
|
||||
def run_command_async(command_key, safe_cmd_list, is_shutdown_or_restart):
|
||||
global command_in_progress
|
||||
@@ -367,7 +371,23 @@ def send_heartbeat():
|
||||
t = threading.Thread(target=handle_file_request_async, args=(fname,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
|
||||
# Server-configured SHIFT folder for this node
|
||||
global SHIFT_PATH
|
||||
sp = res.get("shift_path")
|
||||
if sp:
|
||||
SHIFT_PATH = sp
|
||||
|
||||
# Handle video search requests
|
||||
squery = res.get("search", "none")
|
||||
if squery and squery != "none":
|
||||
global search_in_progress
|
||||
if not search_in_progress:
|
||||
search_in_progress = True
|
||||
t = threading.Thread(target=run_file_search, args=(squery,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
print("[*] Heartbeat successful.")
|
||||
except Exception as e:
|
||||
print(f"[!] Failed heartbeat: {e}")
|
||||
@@ -685,6 +705,75 @@ def list_takeleap_subfolder_files(subfolder_name):
|
||||
def list_takeleap_subfolder(subfolder_name):
|
||||
list_takeleap_subfolder_files(subfolder_name)
|
||||
|
||||
def find_shift_roots():
|
||||
# The dashboard-configured path wins; without one, fall back to any
|
||||
# */TAKELEAP/SHIFT discovered by the legacy drive scan.
|
||||
if SHIFT_PATH and os.path.isdir(SHIFT_PATH):
|
||||
return [SHIFT_PATH]
|
||||
roots = []
|
||||
if os.name == "nt":
|
||||
drives = [f"{letter}:\\" for letter in string.ascii_uppercase if os.path.exists(f"{letter}:\\")]
|
||||
else:
|
||||
drives = ["/mnt", "/media", "/home"]
|
||||
if os.path.exists("/TAKELEAP"):
|
||||
drives.append("/TAKELEAP")
|
||||
for drive in drives:
|
||||
for root, dirs, files in os.walk(drive, topdown=True):
|
||||
if os.name == "nt":
|
||||
dirs[:] = [d for d in dirs if d.lower() not in {"windows", "program files", "program files (x86)", "appdata", "programdata", "$recycle.bin", "system volume information", "node_modules", ".git"}]
|
||||
else:
|
||||
if root == "/":
|
||||
dirs[:] = [d for d in dirs if d.lower() not in {"proc", "sys", "dev", "var", "lib", "run", "boot", "snap", "node_modules", ".git"}]
|
||||
elif root == "/mnt":
|
||||
dirs[:] = [d for d in dirs if not (len(d) == 1 and d.isalpha()) and d.lower() not in {"wslg", "wsl"}]
|
||||
if os.path.basename(root).upper() == "TAKELEAP":
|
||||
for sub in dirs:
|
||||
if sub.upper() == "SHIFT":
|
||||
roots.append(os.path.join(root, sub))
|
||||
dirs[:] = [] # don't descend into an already-claimed subtree
|
||||
return roots
|
||||
|
||||
def run_file_search(query):
|
||||
# Case-insensitive filename substring search in the SHIFT folder(s);
|
||||
# results are posted back for the dashboard to render.
|
||||
global search_in_progress
|
||||
MAX_RESULTS = 200
|
||||
try:
|
||||
target = query.lower()
|
||||
roots = find_shift_roots()
|
||||
results = []
|
||||
for base in roots:
|
||||
for froot, fdirs, ffiles in os.walk(base):
|
||||
for f in ffiles:
|
||||
if target in f.lower():
|
||||
fp = os.path.join(froot, f)
|
||||
entry = {"name": f, "path": fp}
|
||||
try:
|
||||
st = os.stat(fp)
|
||||
entry["size_mb"] = round(st.st_size / (1024 * 1024), 2)
|
||||
entry["modified"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(st.st_mtime))
|
||||
except Exception:
|
||||
pass
|
||||
results.append(entry)
|
||||
if len(results) >= MAX_RESULTS:
|
||||
break
|
||||
if len(results) >= MAX_RESULTS:
|
||||
break
|
||||
if len(results) >= MAX_RESULTS:
|
||||
break
|
||||
payload = {"query": query, "results": results, "searched_path": ", ".join(roots)}
|
||||
if not roots:
|
||||
payload["error"] = "No SHIFT folder configured or found on this system"
|
||||
data = json.dumps(payload).encode('utf-8')
|
||||
req = urllib.request.Request(f"{CENTRAL_BASE}/api/search-results?client_id={CLIENT_ID}", data=data, headers={'Content-Type': 'application/json'}, method='POST')
|
||||
req.add_header('ngrok-skip-browser-warning', 'true')
|
||||
urllib.request.urlopen(req, timeout=30)
|
||||
print(f" -> Search '{query}': posted {len(results)} match(es).")
|
||||
except Exception as e:
|
||||
print(f" -> [!] File search failed: {e}")
|
||||
finally:
|
||||
search_in_progress = False
|
||||
|
||||
# ============================================================
|
||||
# Remote File Transfer: dashboard requests a file by name,
|
||||
# agent finds it under any TAKELEAP folder and uploads it.
|
||||
@@ -725,6 +814,23 @@ def find_file_in_takeleap(filename):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Configured SHIFT path takes priority — SR/SHIFT sites have no TAKELEAP
|
||||
# folder anywhere, so the legacy scan below can never find their files.
|
||||
if SHIFT_PATH and os.path.isdir(SHIFT_PATH):
|
||||
if fast_path_rel:
|
||||
# SHIFT_PATH ends in .../SHIFT and fast_path_rel starts with SHIFT/,
|
||||
# so join the date subpath onto SHIFT_PATH's parent.
|
||||
specific_dir = os.path.join(os.path.dirname(SHIFT_PATH), fast_path_rel)
|
||||
if os.path.exists(specific_dir):
|
||||
for froot, fdirs, ffiles in os.walk(specific_dir):
|
||||
for f in ffiles:
|
||||
if f.lower() == target:
|
||||
return os.path.join(froot, f)
|
||||
for froot, fdirs, ffiles in os.walk(SHIFT_PATH):
|
||||
for f in ffiles:
|
||||
if f.lower() == target:
|
||||
return os.path.join(froot, f)
|
||||
|
||||
if os.name == "nt":
|
||||
drives = [f"{letter}:\\" for letter in string.ascii_uppercase if os.path.exists(f"{letter}:\\")]
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user