#!/usr/bin/env python3 """ Linux 命令沙盒练习平台 Server(增强版) - 集成 sandbox.py 沙盒引擎 - 提供课程、命令执行、任务判定、学习建议等 API """ from __future__ import annotations import hashlib import http.server import json import os import urllib.parse from typing import Any from sandbox import LinuxSandbox USERS = { "admin": hashlib.sha256(b"safe_linux_2026").hexdigest(), } TOKEN_TTL = 86400 TASKS_FILE = os.path.join(os.path.dirname(__file__), "COURSE_TASKS.json") HTML_FILE = os.path.join(os.path.dirname(__file__), "index.html") SANDBOX = LinuxSandbox() def load_tasks() -> dict[str, Any]: try: with open(TASKS_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print(f"Error loading tasks: {e}") return {"meta": {}, "levels": []} TASKS = load_tasks() def find_task(task_id: str) -> dict[str, Any] | None: for level in TASKS.get("levels", []): for task in level.get("challenges", []): if task.get("id") == task_id: task["level_title"] = level.get("title", "") task["level_id"] = level.get("id", "") return task return None class LinuxSandboxHandler(http.server.BaseHTTPRequestHandler): def log_message(self, format, *args): pass def send_json(self, data: dict[str, Any], status=200): raw = json.dumps(data, ensure_ascii=False).encode("utf-8") self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(raw))) self.end_headers() self.wfile.write(raw) def _send_file(self, path: str, content_type: str): with open(path, "rb") as f: content = f.read() self.send_response(200) self.send_header("Content-Type", content_type) self.send_header("Content-Length", str(len(content))) self.end_headers() self.wfile.write(content) def check_auth(self, auth_header: str, token: str) -> bool: if self.client_address[0] == "127.0.0.1": return True if token == "safe_linux_2026": return True if auth_header.startswith("Bearer ") and auth_header[7:] == "safe_linux_2026": return True return False def do_GET(self): parsed = urllib.parse.urlparse(self.path) path = parsed.path if path not in ["/", "/login.html", "/api/login", "/api/logout", "/api/tasks", "/api/health"]: auth_header = self.headers.get("Authorization", "") token = self.headers.get("X-Token", "") if not self.check_auth(auth_header, token) and "xiaoxiaoluohao.indevs.in" in self.headers.get("Host", ""): self.send_json({"error": "Authentication required"}, 401) return if path == "/": self._send_file(HTML_FILE, "text/html; charset=utf-8") return if path == "/api/health": self.send_json({"ok": True, "cwd": SANDBOX.cwd, "user": SANDBOX.user}) return if path == "/api/run": query = urllib.parse.parse_qs(parsed.query) cmd = query.get("cmd", [""])[0] if not cmd: self.send_json({"error": "No command provided"}, 400) return result = SANDBOX.execute(cmd) self.send_json(result) return if path == "/api/check": query = urllib.parse.parse_qs(parsed.query) task_id = query.get("task_id", [""])[0] cmd = query.get("last_cmd", [""])[0] output = query.get("output", [""])[0] sandbox_state = { "cwd": SANDBOX.cwd, "exists": SANDBOX.exists, "is_executable": SANDBOX.is_executable, "cmd": cmd, "output": output, } if not task_id: self.send_json({"error": "task_id required"}, 400) return task = find_task(task_id) if not task: self.send_json({"error": "Task not found"}, 404) return success, reason = self.evaluate_task(task, sandbox_state) self.send_json({ "task_id": task_id, "success": success, "message": task.get("success_msg", "✅ 回答正确!") if success else reason or task.get("fail_message", "❌ 未通过测试"), "hint": task.get("hint", "继续尝试,或者看看相关命令示例"), "title": task.get("title", "未知任务"), "next_suggestion": self.build_next_suggestion(task_id), }) return if path == "/api/tasks": self.send_json(TASKS) return self.send_response(404) self.end_headers() self.wfile.write(b"Not Found") def do_POST(self): parsed = urllib.parse.urlparse(self.path) path = parsed.path content_length = int(self.headers.get("Content-Length", 0)) raw = self.rfile.read(content_length).decode() if content_length else "{}" if path == "/api/login": try: data = json.loads(raw) except Exception: self.send_json({"success": False, "error": "Invalid JSON"}, 400) return username = data.get("username", "") password = data.get("password", "") if username in USERS and hashlib.sha256(password.encode()).hexdigest() == USERS[username]: self.send_json({"success": True, "token": "safe_linux_2026", "message": "✅ 登录成功!"}) return self.send_json({"success": False, "error": "❌ 用户名或密码错误"}, 401) return if path == "/api/logout": self.send_json({"success": True, "message": "👋 已退出登录"}) return if path == "/api/reset": SANDBOX.reset() self.send_json({"success": True, "message": "♻️ 沙盒环境已重置"}) return self.send_response(404) self.end_headers() self.wfile.write(b"Not Found") def evaluate_task(self, task: dict[str, Any], state: dict[str, Any]) -> tuple[bool, str]: cmd = state["cmd"] output = state["output"] success_test = task.get("success_test") if task.get("solution"): for sol in task["solution"]: if cmd.strip() == sol.strip(): return True, "" if not success_test: return False, "❌ 还没达到任务要求" try: ok = bool(eval(success_test, {"__builtins__": {}}, state)) return ok, "❌ 命令执行了,但结果还没达到题目要求" except Exception: return False, "❌ 当前判题规则未命中,换个更准确的命令试试" def build_next_suggestion(self, current_task_id: str) -> str | None: all_tasks = [] for level in TASKS.get("levels", []): all_tasks.extend(level.get("challenges", [])) for idx, task in enumerate(all_tasks): if task.get("id") == current_task_id and idx + 1 < len(all_tasks): nxt = all_tasks[idx + 1] return f"下一题:{nxt.get('title', '继续挑战')}" return None if __name__ == "__main__": PORT = 8084 print(f"🌱 Linux Sandbox Practice Server 启动中... http://127.0.0.1:{PORT}") print("📚 地址: https://linux.xiaoxiaoluohao.indevs.in") print("🔑 Account: admin / safe_linux_2026") http.server.ThreadingHTTPServer(("127.0.0.1", PORT), LinuxSandboxHandler).serve_forever()