Files
linux-practice/server.py

215 lines
7.7 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()