Compare commits

...

11 Commits

9 changed files with 4127 additions and 1843 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Python caches
__pycache__/
*.pyc
# OpenClaw interactive edit backups
*.interactive.backup.*

330
COURSE.md
View File

@@ -1,147 +1,281 @@
# 📚 Linux 命令学习课程体系(入门 → 高手
# Linux 学习平台课程设计(运维全场景重构版
> 学习路径:**理论 → 演示 → 沙盒练习 → 测试 → 徽章认证**
## 总目标
这套课程不再只是 Linux 命令入门,也不是答题闯关页,而是:
> **面向运维强相关场景的 Linux 系统学习平台**
目标是帮助学习者逐步形成三层能力:
1. **命令理解能力**:知道命令在解决什么问题
2. **系统认知能力**:知道 Linux 系统是怎么组织和运行的
3. **运维场景能力**:知道遇到问题时该用哪组命令排查和处理
---
## 🌱 Level 1入门新手村
## 设计原则
### 🎯 目标:熟悉终端、查看文件、当前目录、简单操作
| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 |
|------|------|---------|-------------|---------|
| 1 | `pwd` - 我在哪? | 当前工作目录 | `pwd` 返回 `/sandbox` | 问:你现在的位置是? |
| 2 | `ls` - 看看周围 | 列出目录内容 | `ls` 显示 `users projects logs` | 找出 `/sandbox` 下有几个子目录? |
| 3 | `cd` - 移动位置 | 切换目录 | `cd users` → 进入用户区 | 从 `/sandbox` 进到 `/projects` |
| 4 | `echo` - 说话 | 打印文本 | `echo "Hello Linux"` | 打印你的名字 |
| 5 | `cat` - 看内容 | 查看文件内容 | `cat users/alice.txt` | 读出 alice.txt 的内容 |
**徽章**` beginner_1 ← 目录旅行者`
- **先理解,再操作**
- **先场景,再命令**
- **先最小可用,再扩展参数**
- **练习服务于理解,不反客为主**
- **从经典 Linux / Unix 教材与运维认证体系中吸收结构感**
- **尽量覆盖运维强相关全场景**
---
## 🚀 Level 2文件操作手艺人
## 课程组织方式
### 🎯 目标:创建、复制、移动、删除(安全版)、查看
课程采用三层结构:
| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 |
|------|------|---------|-------------|---------|
| 6 | `touch` - 创建空文件 | 创建新文件 | `touch mynote.txt` | 创建一个名为 `test.txt` 的文件 |
| 7 | `cp` - 复制 | 复制文件/目录 | `cp users/alice.txt backup/` | 复制 `project/backend/app.py``archive/` |
| 8 | `mv` - 移动/重命名 | 移动或重命名 | `mv old.txt new.txt` | 把 `logs/access.log` 重命名为 `old_access.log` |
| 9 | `mkdir` - 创建目录 | 创建级联目录 | `mkdir -p a/b/c` | 创建 `myproject/src/main` |
| 10 | `head/tail` - 看头尾 | 查看文件前/后几行 | `tail -n 5 logs/access.log` | 查看 `app.py` 最后 3 行 |
### 第一层:知识模块
按 Linux/运维核心领域拆分模块。
**徽章**` beginner_2 ← 文件管理员`
### 第二层:课时
每个课时围绕一个核心问题或一组密切相关命令展开。
### 第三层:练习
练习分为:
- 理解题
- 操作题
- 场景题
练习是为了巩固,不再主导整个平台体验。
---
## 🔍 Level 3搜索高手情报员
## 完整课程蓝图10 大模块
### 🎯 目标:快速定位、查找、筛选内容
### 模块 1建立 Linux 基本认知
目标:搞清楚终端、路径、目录、文件的基本世界观。
| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 |
|------|------|---------|-------------|---------|
| 11 | `grep` - 搜索关键词 | 正则匹配文本 | `grep "Linux" *.txt` | 在 `users/` 下找包含 "Alice" 的文件 |
| 12 | `find` - 按条件找文件 | 时间/大小/类型 | `find /logs -type f -name "*.log"` | 找出所有 `.py` 文件 |
| 13 | `du` - 查看占用 | 磁盘使用情况 | `du -sh *` | 评估 `/projects` 每个子目录大小 |
| 14 | `sort` - 排序 | 排序输出 | `ls \| sort` | 按文件大小升序排列 `/logs` |
| 15 | `wc` - 统计 | 行/词/字节数 | `wc -l app.py` | `find /projects -type f \| wc -l` 有几个文件? |
包含:
- 终端与 Shell
- 绝对路径 / 相对路径
- pwd / ls / cd / cat / echo
**徽章**` intermediate_1 ← 情报专家`
输出能力:
- 能定位自己
- 能看懂基本路径
- 能读基础文件内容
---
## 🛠️ Level 4文本编辑文字工作者
### 模块 2文件与目录操作
目标:建立文件系统操作能力。
### 🎯 目标:预览/编辑文本(只读模式)
包含:
- 文件和目录的区别
- mkdir / touch / cp / mv / rm / stat
- 文件生命周期:创建、备份、迁移、清理
| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 |
|------|------|---------|-------------|---------|
| 16 | `nano/vim` 基础 | 只读模式(演示) | `view project/backend/app.py` | 显示文件内容(用 `cat` 替代 vim |
| 17 | `>`/`>>` 重定向 | 输出到文件 | `echo "test" > test.txt` | 把 `grep "def" app.py` 结果保存到 `methods.txt` |
| 18 | `|` 管道 | 连接命令 | `cat users/* \| grep Alice` | 找出所有包含 "Linux" 的内容 |
**徽章**` intermediate_2 ← 文字工匠`
输出能力:
- 能完成基础文件管理
- 能理解文件操作的风险和意义
---
## 🔐 Level 5高级技巧小黑客
### 模块 3阅读与筛选信息
目标:建立日志、配置、文本信息处理能力。
### 🎯 目标:权限、查找大文件、进程、自动化
包含:
- head / tail / grep / wc / sort / find
- 搜索与筛选思路
- 日志阅读思路
| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 |
|------|------|---------|-------------|---------|
| 19 | `find -size` - 找大文件 | 按大小查找 | `find / -size +100M` | 找出 `/logs` 下大于 10KB 的文件 |
| 20 | `chmod` 原理 | 权限概念(只读) | 解释 `rwxr-xr-x` | 问:`644` 是什么权限? |
| 21 | `ps`/`top` 基础 | 进程概念 | `ps aux \| grep python` | 找出所有 `java` 进程 |
| 22 | `history` - 命令历史 | 查看历史 | `history \| tail -n 10` | 看最近 3 条执行的命令 |
| 23 | `man` - 查手册 | 查帮助 | `man ls`(模拟) | 问:`ls -a` 是什么作用? |
**徽章**` advanced_1 ← 系统法师`
输出能力:
- 能读日志
- 能找关键词
- 能定位文件
- 能做基础统计
---
## 🎓 Level 6实战项目通关玩家
### 模块 4系统状态与资源认知
目标:知道一台 Linux 机器此刻在运行什么。
### 🎯 综合应用:解决真实场景
包含:
- 进程、负载、CPU、内存、磁盘、挂载点
- ps / top / uptime / free / df / du / mount / lsof
| 场景 | 任务 | 所需命令 | 难度 |
|------|------|----------|------|
| 📁 备份项目 | 将 `/projects` 下所有 `.py` 文件备份到 `/backup` | `find`, `cp`, `mkdir` | ⚔️⚔️⚔️ |
| 🔎 搜索日志 | 找出所有包含 `"error"` 的日志行 | `grep`, `find`, `cat` | ⚔️⚔️⚔️⚔️ |
| 📏 磁盘分析 | 写出 `/projects` 中最大的 3 个文件 | `du`, `sort`, `tail` | ⚔️⚔️ |
| 📝 生成报告 | 把所有 `.py` 文件的行数统计保存到 `stats.txt` | `wc`, `find`, `>` | ⚔️⚔️⚔️⚔️⚔️ |
| 🔐 权限检查 | 找出所有`.sh` 脚本并检查权限是否为 `755` | `find`, `stat`, `grep` | ⚔️⚔️⚔️⚔️⚔️ |
输出能力:
- 能做基础资源排查
- 能理解系统是否正常运行
---
## 🏆 完整通关徽章体系
### 模块 5服务与日志排障
目标:围绕服务故障建立排查链路。
```bash
beginner_1 ← 目录旅行者 pwd/ls/cd
beginner_2 ← 文件管理员 touch/cp/mv/mkdir
intermediate_1 ← 情报专家 grep/find/du/sort/wc
intermediate_2 ← 文字工匠 cat/echo/pipe/redirection
advanced_1 ← 系统法师 find-size/chmod/ps/history/man
expert_1 ← 实战大师 (综合项目通关)
legend ← Linux 大师 (所有课程 + 心得分享)
```
包含:
- systemd 基础
- 服务状态 / 启停 / 自启动
- journalctl
- 进程、端口、日志之间的关系
- systemctl / service / journalctl / kill / pkill / nohup
输出能力:
- 能处理“服务没起来 / 起了但不可用 / 日志报错”类问题
---
## 📝 测验模式设计
### 模块 6网络与连接排查
目标:建立网络、监听、请求和服务可达性的认知。
每个课时结束后,自动弹出
包含
- 网卡 / IP / 端口 / 监听 / 连通性
- ifconfig / ip addr / ping / ss / netstat / curl / wget / traceroute / dig
```bash
🎯 当前任务_____________
💡 提示_________________
(stdin) > [输入命令]
✅ 回答正确!获得经验值 +100
❌ 还未达标!提示:试试 `xxx`
```
答对 3 次 → 解锁下一关
输出能力:
- 能判断服务通不通
- 能区分网络层、端口层、HTTP 层的问题
---
## 🎯 课程特色
### 模块 7权限、用户与安全基础
目标:从“能执行”提升到“知道该不该执行”。
-**零风险沙盒**:所有命令在虚拟环境中执行,不会影响真实系统
- **闯关式学习**:从入门到高手,逐步解锁新技能
- **即时反馈**:答对/错都有针对性提示
- **实战导向**:每个级别都有真实业务场景
- **徽章认证**:每完成一个阶段获得专属徽章
包含:
- 用户、组、权限模型
- chmod / chown / chgrp / whoami / id / passwd / sudo / su
- 最小权限原则
- 高风险命令意识
输出能力:
- 能处理基础权限问题
- 能理解安全边界
---
需要我根据这个课程体系开始实现吗?包括:
1. `COURSE_TASKS.json`(所有练习题)
2. 沙盒模拟器 `sandbox.py`
3. 熟悉 `server.py` 重构
4. UI 改造(闯关式界面)
5. `README.md` 使用文档
### 模块 8软件包、环境与命令定位
目标:理解软件从哪里来、命令为什么能执行、版本怎么查。
还是先调整下课程内容?😄
包含:
- PATH 与命令查找
- which / whereis / env / export / alias
- apt / dpkg / yum / rpm
输出能力:
- 能定位命令来源
- 能查版本、查安装包、查环境变量问题
---
### 模块 9自动化、归档与运维习惯
目标:建立“批量处理、定时执行、可重复操作”的意识。
包含:
- 重定向 / 管道
- crontab
- tar / gzip / zip / unzip
- history
- Shell 习惯
输出能力:
- 能做基础自动化
- 能做备份和简单定时任务
- 能形成更稳妥的命令行习惯
---
### 模块 10运维综合实战场景
目标:把前面所有能力真正串起来。
典型场景:
- 服务无法访问排查
- 磁盘爆满排查
- 登录失败排查
- Nginx / 应用日志排查
- 发布后服务未启动
- 备份与恢复演练
输出能力:
- 知道不是背命令,而是围绕问题组织命令链路
---
## 每课统一结构
每个课时都按下列结构组织:
1. **学什么**
2. **为什么重要**
3. **核心知识点**
4. **最小示例**
5. **常见误区**
6. **典型场景**
7. **练习题**
8. **课后总结 / 迁移建议**(后续补齐)
---
## 练习设计原则
### A. 理解题
检查有没有理解命令用途和思路。
### B. 操作题
检查是否能写出正确命令。
### C. 场景题
检查是否能把命令放进真实运维问题中。
> 练习不是为了“刷过去”,而是为了确认:你是不是真的知道这个命令为什么存在。
---
## 页面结构原则
### 左侧:课程地图
- 模块
- 课时
- 学习路径
### 中间:正文学习区
- 讲解优先
- 示例优先
- 场景优先
### 右侧:辅助理解区
- 易错点
- 相关命令
- 场景提示
- 理解型问题
### 底部 / 内嵌:轻练习区
- 练习服务于学习
- 保持必要但不过分喧宾夺主
---
## 吸收的“教材气质”
这套平台后续会持续内化这些来源的设计方法:
- 经典 Linux / Unix 入门教材的结构感
- man / 官方文档的准确性
- RHCSA / RHCE / LPIC 一类认证体系的能力递进
- 真实运维工作流里的问题链路
不是搬教材,而是:
> **把经典教材与运维经验内化后,重新设计成适合平台学习的课程。**
---
## 当前重构阶段
### 已完成
- 平台方向从交互优先改成学习优先
- 前 3 个模块已落到课程结构中
- 页面结构已切为知识正文型
### 下一步
- 扩完整个 10 模块蓝图到课程数据
- 逐步补模块 4~10 的详细课时
- 增加“场景专题”页,把命令真正串成运维链路
---
## 一句话定位
> **这是一个吸收经典 Linux 教材与运维训练体系后,面向运维强相关全场景重构的 Linux 学习平台。**

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

1404
index.html

File diff suppressed because it is too large Load Diff

67
privacy.html Normal file
View File

@@ -0,0 +1,67 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Bot 隐私政策 / Privacy Policy</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "PingFang SC", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif; line-height: 1.6; margin: 0; padding: 24px; color: #111; }
main { max-width: 860px; margin: 0 auto; }
h1,h2 { line-height: 1.25; }
code { background: #f6f8fa; padding: 2px 6px; border-radius: 6px; }
.muted { color: #555; }
ul { padding-left: 18px; }
hr { border: 0; border-top: 1px solid #eee; margin: 20px 0; }
</style>
</head>
<body>
<main>
<h1>Bot 隐私政策 / Privacy Policy</h1>
<p class="muted">最后更新2026-03-10</p>
<h2>1. 我们是谁</h2>
<p>本隐私政策适用于与本 Telegram Bot以下简称“Bot”的交互。本 Bot 用于提供个人助理与技术学习/排障相关的对话服务。</p>
<h2>2. 我们收集哪些数据</h2>
<ul>
<li><b>你发送的消息内容</b>(文本、命令、你主动提供的上下文)。</li>
<li><b>基础元数据</b>(如 Telegram 提供的 chat_id、消息时间戳、用户名/昵称等,用于路由回复与防滥用)。</li>
<li><b>运行日志</b>:为排障与稳定性,系统可能记录错误日志与请求失败信息(可能包含部分消息片段或上下文)。</li>
</ul>
<h2>3. 数据如何被使用</h2>
<ul>
<li>用于生成回复、执行你请求的任务(例如课程内容生成、命令模拟、排障建议)。</li>
<li>用于安全控制与反滥用(例如鉴权、速率限制)。</li>
<li>用于系统稳定性与问题定位例如网络错误、API 调用失败)。</li>
</ul>
<h2>4. 数据共享与第三方</h2>
<p>Bot 运行依赖以下第三方服务,数据会按功能需要流转:</p>
<ul>
<li><b>Telegram</b>:消息收发与基础元数据由 Telegram 处理。</li>
<li><b>模型/推理服务提供商</b>:为生成回复,消息内容可能会被发送到大模型推理服务(以完成你的请求)。</li>
<li><b>托管/网络服务</b>:用于运行 Bot 与相关服务的基础设施提供商可能处理网络请求与日志。</li>
</ul>
<p>我们不会将你的个人数据出售给任何第三方。</p>
<h2>5. 数据保留</h2>
<p>我们会在实现功能与保障稳定性的必要范围内保留数据(例如对话上下文、课程数据、运行日志)。保留时间可能因用途不同而不同。</p>
<h2>6. 你的权利</h2>
<ul>
<li>你可以随时停止使用 Bot。</li>
<li>你可以请求导出或删除与你相关的本地存储数据(在技术可行范围内)。</li>
</ul>
<h2>7. 安全</h2>
<p>我们采取合理的技术措施保护数据(例如服务访问控制、最小权限原则)。但任何系统都无法保证绝对安全。</p>
<h2>8. 联系方式</h2>
<p>如需数据删除/导出或有隐私问题,请通过你与 Bot 的对话渠道联系管理员。</p>
<hr />
<p class="muted">English summary: We process the messages you send to provide replies and task automation. Basic Telegram metadata and operational logs may be stored for reliability and debugging. Data may be transmitted to Telegram and model providers as needed to generate responses. We do not sell personal data. You may request deletion/export where feasible.</p>
</main>
</body>
</html>

1254
sandbox.py

File diff suppressed because it is too large Load Diff

395
server.py
View File

@@ -1,285 +1,232 @@
#!/usr/bin/env python3
"""
Linux 命令沙盒练习平台 Server
- 集成 sandbox.py 沙盒引擎
- 路由:/ → HTML 页面, /api/run → 命令执行, /api/check → 任务检查
Linux 习平台 Server(知识导向版)
- 提供课程结构、练习判题、沙盒执行
- 课程模型module -> lesson -> exercise
"""
from __future__ import annotations
import hashlib
import http.server
import urllib.parse
import json
import os
import base64
import hashlib
import urllib.parse
from typing import Any
# ==============================
# 认证配置
# ==============================
# 预置用户:{username: password_hash}
# 生成方式hashlib.sha256(b"password").hexdigest()
USERS = {
# 默认管理员账户
"admin": hashlib.sha256(b"safe_linux_2026").hexdigest(),
# 可添加更多用户
}
# Token 有效期(秒)
TOKEN_TTL = 86400 # 24 小时
def check_auth(request_body: bytes = None) -> tuple[bool, str]:
"""
检查 Authorization 请求头
支持: Bearer token 或 Basic auth
"""
# 默认允许访问(未启用强制认证)
# 启用:true 改为 True
ENABLE_AUTH = False
if not ENABLE_AUTH:
return True, "admin"
if not request_body:
return False, "Missing Authorization header"
# 解析请求头
# 实际在 do_GET 中通过 headers 获取
return True, "admin" # 暂时 Allow-All后续优化
# 导入沙盒模块
from sandbox import LinuxSandbox
USERS = {
"admin": hashlib.sha256(b"safe_linux_2026").hexdigest(),
}
TASKS_FILE = os.path.join(os.path.dirname(__file__), "COURSE_TASKS.json")
HTML_FILE = os.path.join(os.path.dirname(__file__), "index.html")
PRIVACY_FILE = os.path.join(os.path.dirname(__file__), "privacy.html")
SANDBOX = LinuxSandbox()
# ==============================
# 任务数据加载
# ==============================
TASKS_FILE = os.path.join(os.path.dirname(__file__), "COURSE_TASKS.json")
def load_tasks():
"""加载课程任务"""
def load_course() -> 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 {"levels": []}
print(f"Error loading course: {e}")
return {"meta": {}, "modules": []}
TASKS = load_tasks()
COURSE = load_course()
# ==============================
# Handler 类
# ==============================
class LinuxSandboxHandler(http.server.BaseHTTPRequestHandler):
def send_json(self, data, status=200):
"""发送 JSON 响应"""
def flatten_exercises() -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for module in COURSE.get("modules", []):
for lesson in module.get("lessons", []):
for exercise in lesson.get("exercises", []):
item = dict(exercise)
item["module_id"] = module.get("id")
item["module_title"] = module.get("title")
item["lesson_id"] = lesson.get("id")
item["lesson_title"] = lesson.get("title")
item["lesson_goal"] = lesson.get("goal")
item["lesson_command"] = lesson.get("command")
rows.append(item)
return rows
def find_exercise(ex_id: str) -> dict[str, Any] | None:
for item in flatten_exercises():
if item.get("id") == ex_id:
return item
return None
class LinuxLearningHandler(http.server.BaseHTTPRequestHandler):
def log_message(self, format, *args):
pass
def send_json(self, data: 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(json.dumps(data, ensure_ascii=False).encode("utf-8"))
self.wfile.write(raw)
def send_text(self, text, status=200):
"""发送纯文本响应"""
self.send_response(status)
self.send_header("Content-Type", "text/plain; charset=utf-8")
def send_file(self, path: str, content_type: str):
with open(path, "rb") as f:
body = f.read()
self.send_response(200)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(text.encode("utf-8"))
self.wfile.write(body)
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
# 🔐 认证检查(跳过 / 登录页 / API 登录)
if path not in ["/", "/login.html", "/api/login", "/api/logout"]:
# 检查 Cookie 或 Authorization
if path not in ["/", "/privacy", "/privacy.html", "/api/login", "/api/logout", "/api/course", "/api/health"]:
auth_header = self.headers.get("Authorization", "")
token = self.headers.get("X-Token", "")
# 简化:如果带了有效 token 或直接 local 环境127.0.0.1)则放行
if not self.check_auth(auth_header, token):
# 对公网域名强制认证
if "xiaoxiaoluohao.indevs.in" in self.headers.get("Host", ""):
self.send_json({"error": "Authentication required"}, 401)
return
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
# 1. 主页:返回 HTML 页面
if path == "/":
html_path = os.path.join(os.path.dirname(__file__), "index.html")
try:
with open(html_path, "rb") as f:
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(f.read())
except Exception as e:
self.send_response(500)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(f"Error: {e}".encode())
self.send_file(HTML_FILE, "text/html; charset=utf-8")
return
# 2. 命令执行 API
elif path == "/api/run":
if path in {"/privacy", "/privacy.html"}:
self.send_file(PRIVACY_FILE, "text/html; charset=utf-8")
return
if path == "/api/health":
self.send_json({"ok": True, "cwd": SANDBOX.cwd, "user": SANDBOX.user, "modules": len(COURSE.get("modules", []))})
return
if path == "/api/course":
self.send_json(COURSE)
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
self.send_json(SANDBOX.execute(cmd))
return
# 执行沙盒命令
result = SANDBOX.execute(cmd)
self.send_json(result)
# 3. 任务检查 API
elif path == "/api/check":
if path == "/api/check":
query = urllib.parse.parse_qs(parsed.query)
task_id = query.get("task_id", [""])[0]
if not task_id:
self.send_json({"error": "-task_id required"}, 400)
ex_id = query.get("exercise_id", [""])[0]
cmd = query.get("last_cmd", [""])[0]
output = query.get("output", [""])[0]
if not ex_id:
self.send_json({"error": "exercise_id required"}, 400)
return
# 查找任务
task = None
for level in TASKS.get("levels", []):
for t in level.get("challenges", []):
if t.get("id") == task_id:
task = t
break
if task:
break
if not task:
self.send_json({"error": "Task not found"}, 404)
exercise = find_exercise(ex_id)
if not exercise:
self.send_json({"error": "Exercise not found"}, 404)
return
# 获取当前命令执行结果
current_cmd = query.get("last_cmd", [""])[0]
current_output = query.get("output", [""])[0]
# 检查是否匹配
success = False
message = task.get("fail_message", "❌ 未通过测试")
# 尝试匹配 solution
for sol in task.get("solution", []):
if current_cmd.strip() == sol.strip():
success = True
message = "✅ 回答正确!🎉"
break
# 如果没匹配上,检查 success_test 逻辑
if not success and "success_test" in task:
if self.evaluate_test(task["success_test"], current_output):
success = True
message = "✅ 回答正确!🎉"
state = {
"cmd": cmd,
"output": output,
"cwd": SANDBOX.cwd,
"exists": SANDBOX.exists,
"is_executable": SANDBOX.is_executable,
}
success, reason = self.evaluate_exercise(exercise, state)
self.send_json({
"task_id": task_id,
"exercise_id": ex_id,
"success": success,
"message": message,
"hint": task.get("hint", "💡 没有提示"),
"title": task.get("title", "未知任务"),
"message": exercise.get("success_msg", "✅ 练习通过") if success else reason,
"hint": exercise.get("hint"),
"lesson_title": exercise.get("lesson_title"),
"module_title": exercise.get("module_title"),
"next_suggestion": self.build_next_suggestion(ex_id),
})
return
# 4. 获取课程总览
elif path == "/api/tasks":
self.send_json(TASKS)
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not Found")
# 404
else:
self.send_response(404)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"Not Found")
def check_auth(self, auth_header: str, token: str) -> bool:
"""认证检查:支持 Bearer token / X-Token header"""
# 本地环境直接放行
if self.client_address[0] == "127.0.0.1":
return True
# 公网场景:验证 token
if token:
# 简化版:验证预置 token生产环境应加密存储
valid_tokens = ["safe_linux_2026"]
return token in valid_tokens
# Bearer token 支持
if auth_header.startswith("Bearer "):
token = auth_header[7:]
return token in ["safe_linux_2026"]
return False
def evaluate_test(self, test_expr: str, output: str) -> bool:
"""简单评估 success_test 表达式"""
try:
# 支持简单的 if/contains 语法
# if output contains 'xxx' then pass
if "contains" in test_expr:
parts = test_expr.split("'")
if len(parts) >= 3:
keyword = parts[1]
return keyword in output
elif "==" in test_expr:
parts = test_expr.split("==")
if len(parts) == 2:
expected = parts[1].strip().strip("'")
return output.strip() == expected
return False
except Exception:
return False
def log_message(self, format, *args):
pass # Suppress logging
# ==============================
# Auth APIs
# ==============================
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 "{}"
# /api/login
if path == "/api/login":
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length).decode()
try:
data = json.loads(body)
username = data.get("username", "")
password = data.get("password", "")
# 简单验证
if username in USERS:
pwd_hash = hashlib.sha256(password.encode()).hexdigest()
if pwd_hash == USERS[username]:
# 返回成功 token
self.send_json({
"success": True,
"token": "safe_linux_2026",
"message": "✅ 登录成功!"
})
return
self.send_json({"success": False, "error": "❌ 用户名或密码错误"}, 401)
except Exception as e:
self.send_json({"success": False, "error": str(e)}, 400)
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
# /api/logout
elif path == "/api/logout":
if path == "/api/logout":
self.send_json({"success": True, "message": "👋 已退出登录"})
return
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not Found")
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_exercise(self, exercise: dict[str, Any], state: dict[str, Any]) -> tuple[bool, str]:
ex_type = exercise.get("type")
if ex_type in {"understanding", "scenario"}:
return False, "📝 这是理解类练习,请先阅读讲解并思考答案。"
cmd = state["cmd"].strip()
if exercise.get("solution"):
for sol in exercise["solution"]:
if cmd == sol.strip():
return True, ""
success_test = exercise.get("success_test")
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_ex_id: str) -> str | None:
rows = flatten_exercises()
for i, item in enumerate(rows):
if item.get("id") == current_ex_id and i + 1 < len(rows):
nxt = rows[i + 1]
return f"继续下一练:{nxt.get('title') or nxt.get('question') or nxt.get('id')}"
return None
if __name__ == "__main__":
PORT = 8084
print(f"🌱 Linux Sandbox Practice Server with Auth 启动中... http://127.0.0.1:{PORT}")
print("📚 地址: https://linux.xiaoxiaoluohao.indevs.in")
print("🔑 Account: admin / safe_linux_2026")
http.server.HTTPServer(("127.0.0.1", PORT), LinuxSandboxHandler).serve_forever()
port = 8084
print(f"🐧 Linux 学习平台启动中... http://127.0.0.1:{port}")
print("📚 线上地址: https://linux.xiaoxiaoluohao.indevs.in")
http.server.ThreadingHTTPServer(("127.0.0.1", port), LinuxLearningHandler).serve_forever()