From 5686831d9ace02b7657a5608060c6be665ac3417 Mon Sep 17 00:00:00 2001 From: likingcode Date: Sat, 7 Mar 2026 05:43:51 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Linux=E7=BB=83=E4=B9=A0=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Web界面Linux命令练习 - Python后端 + sandbox安全沙箱 - 课程和任务管理 --- COURSE.md | 147 +++++ COURSE_FULL.json | 192 ++++++ COURSE_TASKS.json | 192 ++++++ COURSE_TASKS.json.backup.1772810579 | 121 ++++ COURSE_TASKS.json.bak | 314 ++++++++++ COURSE_TASKS_NEW.json | 314 ++++++++++ COURSE_TASKS_v2.json | 314 ++++++++++ __pycache__/sandbox.cpython-312.pyc | Bin 0 -> 21312 bytes __pycache__/server.cpython-312.pyc | Bin 0 -> 7881 bytes index.html | 926 ++++++++++++++++++++++++++++ index.html.backup.1772810579 | 582 +++++++++++++++++ index.html.backup.test | 582 +++++++++++++++++ index.html.bak | 582 +++++++++++++++++ index.html.bak.1772815479 | 644 +++++++++++++++++++ index.html.debug | 648 +++++++++++++++++++ index.html.v2.bak | 582 +++++++++++++++++ index.html.v3.bak | 582 +++++++++++++++++ index_new.html | 926 ++++++++++++++++++++++++++++ linux-practice.service | 17 + sandbox.py | 581 +++++++++++++++++ server.py | 285 +++++++++ server.py.bak | 285 +++++++++ 22 files changed, 8816 insertions(+) create mode 100644 COURSE.md create mode 100644 COURSE_FULL.json create mode 100644 COURSE_TASKS.json create mode 100644 COURSE_TASKS.json.backup.1772810579 create mode 100644 COURSE_TASKS.json.bak create mode 100644 COURSE_TASKS_NEW.json create mode 100644 COURSE_TASKS_v2.json create mode 100644 __pycache__/sandbox.cpython-312.pyc create mode 100644 __pycache__/server.cpython-312.pyc create mode 100644 index.html create mode 100644 index.html.backup.1772810579 create mode 100644 index.html.backup.test create mode 100644 index.html.bak create mode 100644 index.html.bak.1772815479 create mode 100644 index.html.debug create mode 100644 index.html.v2.bak create mode 100644 index.html.v3.bak create mode 100644 index_new.html create mode 100644 linux-practice.service create mode 100644 sandbox.py create mode 100755 server.py create mode 100755 server.py.bak diff --git a/COURSE.md b/COURSE.md new file mode 100644 index 0000000..3de24d1 --- /dev/null +++ b/COURSE.md @@ -0,0 +1,147 @@ +# 📚 Linux 命令学习课程体系(入门 → 高手) + +> 学习路径:**理论 → 演示 → 沙盒练习 → 测试 → 徽章认证** + +--- + +## 🌱 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 ← 目录旅行者` + +--- + +## 🚀 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 行 | + +**徽章**:` beginner_2 ← 文件管理员` + +--- + +## 🔍 Level 3:搜索高手(情报员) + +### 🎯 目标:快速定位、查找、筛选内容 + +| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 | +|------|------|---------|-------------|---------| +| 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` 有几个文件? | + +**徽章**:` intermediate_1 ← 情报专家` + +--- + +## 🛠️ Level 4:文本编辑(文字工作者) + +### 🎯 目标:预览/编辑文本(只读模式) + +| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 | +|------|------|---------|-------------|---------| +| 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:高级技巧(小黑客) + +### 🎯 目标:权限、查找大文件、进程、自动化 + +| 课时 | 主题 | 学习内容 | 沙盒练习目标 | 测试场景 | +|------|------|---------|-------------|---------| +| 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:实战项目(通关玩家) + +### 🎯 综合应用:解决真实场景 + +| 场景 | 任务 | 所需命令 | 难度 | +|------|------|----------|------| +| 📁 备份项目 | 将 `/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` | ⚔️⚔️⚔️⚔️⚔️ | + +--- + +## 🏆 完整通关徽章体系 + +```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 大师 (所有课程 + 心得分享) +``` + +--- + +## 📝 测验模式设计 + +每个课时结束后,自动弹出: + +```bash +🎯 当前任务:_____________ +💡 提示:_________________ +(stdin) > [输入命令] + +✅ 回答正确!获得经验值 +100 +❌ 还未达标!提示:试试 `xxx` +``` + +答对 3 次 → 解锁下一关 + +--- + +## 🎯 课程特色 + +- ✅ **零风险沙盒**:所有命令在虚拟环境中执行,不会影响真实系统 +- ✅ **闯关式学习**:从入门到高手,逐步解锁新技能 +- ✅ **即时反馈**:答对/错都有针对性提示 +- ✅ **实战导向**:每个级别都有真实业务场景 +- ✅ **徽章认证**:每完成一个阶段获得专属徽章 + +--- + +需要我根据这个课程体系开始实现吗?包括: +1. `COURSE_TASKS.json`(所有练习题) +2. 沙盒模拟器 `sandbox.py` +3. 熟悉 `server.py` 重构 +4. UI 改造(闯关式界面) +5. `README.md` 使用文档 + +还是先调整下课程内容?😄 \ No newline at end of file diff --git a/COURSE_FULL.json b/COURSE_FULL.json new file mode 100644 index 0000000..1ec3d92 --- /dev/null +++ b/COURSE_FULL.json @@ -0,0 +1,192 @@ +{ + "meta": { + "version": "3.0", + "title": "Linux 运维工程师完整教程", + "author": "PMClaw", + "updated": "2026-03-06", + "description": "覆盖菜鸟教程 Linux 全部内容,从入门到精通", + "total_levels": 12, + "total_challenges": 80 + }, + "levels": [ + { + "id": "level_1_basic", + "title": "🌱 Level 1: Linux 基础入门", + "description": "Linux 简介、目录结构、基本操作", + "challenges": [ + {"id": "l1_1_pwd", "title": "查看当前目录", "description": "使用 pwd 命令显示当前工作目录的完整路径", "hint": "直接输入 pwd", "success_test": "'/' in output", "solution": ["pwd"], "success_msg": "📍 定位成功!"}, + {"id": "l1_2_ls", "title": "列出目录内容", "description": "使用 ls 命令查看当前目录下的文件和文件夹", "hint": "输入 ls", "success_test": "len(output) > 0", "solution": ["ls"], "success_msg": "📂 目录内容已显示!"}, + {"id": "l1_3_ls_l", "title": "详细列表", "description": "使用 ls -l 显示详细信息,包括权限、所有者、大小等", "hint": "ls -l", "success_test": "'total' in output or '-' in output[:20]", "solution": ["ls -l"], "success_msg": "📋 详细信息已获取!"}, + {"id": "l1_4_ls_a", "title": "显示隐藏文件", "description": "使用 ls -a 显示包括隐藏文件在内的所有文件", "hint": "ls -a", "success_test": "'.' in output and '..' in output", "solution": ["ls -a"], "success_msg": "👻 隐藏文件已显示!"}, + {"id": "l1_5_cd", "title": "切换目录", "description": "使用 cd 命令进入 /tmp 目录", "hint": "cd /tmp", "success_test": "cwd == '/tmp'", "solution": ["cd /tmp"], "success_msg": "🚶 目录切换成功!"}, + {"id": "l1_6_cd_back", "title": "返回上级目录", "description": "使用 cd .. 返回上级目录", "hint": "cd ..", "success_test": "cwd != '/tmp'", "solution": ["cd .."], "success_msg": "⬆️ 返回成功!"}, + {"id": "l1_7_cd_home", "title": "返回用户主目录", "description": "使用 cd ~ 或 cd 返回用户主目录", "hint": "cd ~", "success_test": "'home' in cwd or cwd == '/'", "solution": ["cd ~", "cd"], "success_msg": "🏠 已回到家目录!"}, + {"id": "l1_8_clear", "title": "清屏", "description": "使用 clear 命令清屏", "hint": "clear", "success_test": "cmd == 'clear'", "solution": ["clear"], "success_msg": "🧹 屏幕已清空!"} + ] + }, + { + "id": "level_2_file", + "title": "📁 Level 2: 文件与目录管理", + "description": "创建、删除、复制、移动文件和目录", + "challenges": [ + {"id": "l2_1_mkdir", "title": "创建目录", "description": "使用 mkdir 创建 /tmp/testdir 目录", "hint": "mkdir /tmp/testdir", "success_test": "exists('/tmp/testdir')", "solution": ["mkdir /tmp/testdir"], "success_msg": "📂 目录创建成功!"}, + {"id": "l2_2_mkdir_p", "title": "递归创建目录", "description": "使用 mkdir -p 创建 /tmp/a/b/c 多级目录", "hint": "mkdir -p /tmp/a/b/c", "success_test": "exists('/tmp/a/b/c')", "solution": ["mkdir -p /tmp/a/b/c"], "success_msg": "📁 多级目录创建成功!"}, + {"id": "l2_3_touch", "title": "创建空文件", "description": "使用 touch 创建 /tmp/test.txt 文件", "hint": "touch /tmp/test.txt", "success_test": "exists('/tmp/test.txt')", "solution": ["touch /tmp/test.txt"], "success_msg": "📄 文件创建成功!"}, + {"id": "l2_4_cp", "title": "复制文件", "description": "将 /etc/hosts 复制到 /tmp/hosts.bak", "hint": "cp /etc/hosts /tmp/hosts.bak", "success_test": "exists('/tmp/hosts.bak')", "solution": ["cp /etc/hosts /tmp/hosts.bak"], "success_msg": "📋 文件复制成功!"}, + {"id": "l2_5_cp_r", "title": "复制目录", "description": "使用 cp -r 复制 /etc/skel 到 /tmp/skel_backup", "hint": "cp -r /etc/skel /tmp/skel_backup", "success_test": "exists('/tmp/skel_backup')", "solution": ["cp -r /etc/skel /tmp/skel_backup"], "success_msg": "📁 目录复制成功!"}, + {"id": "l2_6_mv", "title": "移动文件", "description": "将 /tmp/test.txt 移动到 /tmp/testdir/", "hint": "mv /tmp/test.txt /tmp/testdir/", "success_test": "exists('/tmp/testdir/test.txt')", "solution": ["mv /tmp/test.txt /tmp/testdir/"], "success_msg": "🚚 文件移动成功!"}, + {"id": "l2_7_mv_rename", "title": "重命名文件", "description": "将 /tmp/hosts.bak 重命名为 /tmp/hosts.backup", "hint": "mv /tmp/hosts.bak /tmp/hosts.backup", "success_test": "exists('/tmp/hosts.backup')", "solution": ["mv /tmp/hosts.bak /tmp/hosts.backup"], "success_msg": "✏️ 重命名成功!"}, + {"id": "l2_8_rm", "title": "删除文件", "description": "删除 /tmp/hosts.backup 文件", "hint": "rm /tmp/hosts.backup", "success_test": "not exists('/tmp/hosts.backup')", "solution": ["rm /tmp/hosts.backup"], "success_msg": "🗑️ 文件删除成功!"}, + {"id": "l2_9_rm_r", "title": "删除目录", "description": "使用 rm -r 删除 /tmp/skel_backup 目录", "hint": "rm -r /tmp/skel_backup", "success_test": "not exists('/tmp/skel_backup')", "solution": ["rm -r /tmp/skel_backup"], "success_msg": "🗑️ 目录删除成功!"}, + {"id": "l2_10_rm_rf", "title": "强制删除", "description": "使用 rm -rf 强制删除 /tmp/a 目录及其所有内容", "hint": "rm -rf /tmp/a", "success_test": "not exists('/tmp/a')", "solution": ["rm -rf /tmp/a"], "success_msg": "💥 强制删除成功!"} + ] + }, + { + "id": "level_3_view", + "title": "👁️ Level 3: 文件内容查看", + "description": "cat、more、less、head、tail 等查看命令", + "challenges": [ + {"id": "l3_1_cat", "title": "查看文件内容", "description": "使用 cat 查看 /etc/passwd 文件内容", "hint": "cat /etc/passwd", "success_test": "'root' in output", "solution": ["cat /etc/passwd"], "success_msg": "📖 文件内容已显示!"}, + {"id": "l3_2_cat_n", "title": "显示行号", "description": "使用 cat -n 显示 /etc/passwd 并带行号", "hint": "cat -n /etc/passwd", "success_test": "'1 ' in output or ' 1 ' in output", "solution": ["cat -n /etc/passwd"], "success_msg": "🔢 行号已显示!"}, + {"id": "l3_3_head", "title": "查看开头", "description": "使用 head 查看 /etc/passwd 前 10 行", "hint": "head /etc/passwd", "success_test": "len(output.split('\\n')) >= 5", "solution": ["head /etc/passwd"], "success_msg": "👆 开头内容已显示!"}, + {"id": "l3_4_head_n", "title": "指定行数", "description": "使用 head -n 5 查看前 5 行", "hint": "head -n 5 /etc/passwd", "success_test": "len(output.split('\\n')) <= 7", "solution": ["head -5 /etc/passwd", "head -n 5 /etc/passwd"], "success_msg": "📏 指定行数已显示!"}, + {"id": "l3_5_tail", "title": "查看结尾", "description": "使用 tail 查看 /etc/passwd 最后 10 行", "hint": "tail /etc/passwd", "success_test": "len(output) > 10", "solution": ["tail /etc/passwd"], "success_msg": "👇 结尾内容已显示!"}, + {"id": "l3_6_tail_f", "title": "实时追踪", "description": "使用 tail -f 实时查看 /var/log/syslog(按 Ctrl+C 退出)", "hint": "tail -f /var/log/syslog", "success_test": "cmd.startswith('tail -f')", "solution": ["tail -f /var/log/syslog"], "success_msg": "👀 实时追踪模式!"}, + {"id": "l3_7_more", "title": "分页查看", "description": "使用 more 分页查看 /etc/passwd", "hint": "more /etc/passwd", "success_test": "cmd.startswith('more')", "solution": ["more /etc/passwd"], "success_msg": "📄 分页查看模式!"}, + {"id": "l3_8_less", "title": "可滚动查看", "description": "使用 less 查看 /etc/passwd(支持上下滚动)", "hint": "less /etc/passwd", "success_test": "cmd.startswith('less')", "solution": ["less /etc/passwd"], "success_msg": "📜 可滚动查看模式!"} + ] + }, + { + "id": "level_4_permission", + "title": "🔐 Level 4: 文件权限管理", + "description": "chmod、chown、chgrp 权限控制", + "challenges": [ + {"id": "l4_1_ls_l", "title": "查看权限", "description": "使用 ls -l /etc/passwd 查看文件权限", "hint": "ls -l /etc/passwd", "success_test": "'-' in output or 'r' in output", "solution": ["ls -l /etc/passwd"], "success_msg": "👀 权限信息已显示!"}, + {"id": "l4_2_chmod_755", "title": "修改权限为755", "description": "将 /tmp/testdir 权限改为 rwxr-xr-x (755)", "hint": "chmod 755 /tmp/testdir", "success_test": "cmd == 'chmod 755 /tmp/testdir'", "solution": ["chmod 755 /tmp/testdir"], "success_msg": "🔐 权限修改成功!"}, + {"id": "l4_3_chmod_644", "title": "修改权限为644", "description": "将 /tmp/testdir/test.txt 权限改为 rw-r--r-- (644)", "hint": "chmod 644 /tmp/testdir/test.txt", "success_test": "cmd == 'chmod 644 /tmp/testdir/test.txt'", "solution": ["chmod 644 /tmp/testdir/test.txt"], "success_msg": "🔐 权限修改成功!"}, + {"id": "l4_4_chmod_x", "title": "添加执行权限", "description": "给 /tmp/testdir/test.txt 添加可执行权限", "hint": "chmod +x /tmp/testdir/test.txt", "success_test": "cmd == 'chmod +x /tmp/testdir/test.txt'", "solution": ["chmod +x /tmp/testdir/test.txt"], "success_msg": "⚡ 执行权限已添加!"}, + {"id": "l4_5_chmod_r", "title": "移除读权限", "description": "移除 /tmp/testdir/test.txt 的读权限", "hint": "chmod -r /tmp/testdir/test.txt", "success_test": "cmd == 'chmod -r /tmp/testdir/test.txt'", "solution": ["chmod -r /tmp/testdir/test.txt"], "success_msg": "🚫 读权限已移除!"}, + {"id": "l4_6_chown", "title": "修改所有者", "description": "将 /tmp/testdir/test.txt 所有者改为 root", "hint": "chown root /tmp/testdir/test.txt", "success_test": "cmd.startswith('chown')", "solution": ["chown root /tmp/testdir/test.txt"], "success_msg": "👤 所有者已修改!"}, + {"id": "l4_7_chgrp", "title": "修改所属组", "description": "将 /tmp/testdir/test.txt 所属组改为 root", "hint": "chgrp root /tmp/testdir/test.txt", "success_test": "cmd.startswith('chgrp')", "solution": ["chgrp root /tmp/testdir/test.txt"], "success_msg": "👥 所属组已修改!"}, + {"id": "l4_8_chown_r", "title": "递归修改", "description": "递归修改 /tmp/testdir 及其所有内容的所有者为 root", "hint": "chown -R root /tmp/testdir", "success_test": "cmd.startswith('chown -R')", "solution": ["chown -R root /tmp/testdir"], "success_msg": "🔄 递归修改成功!"} + ] + }, + { + "id": "level_5_search", + "title": "🔍 Level 5: 搜索与过滤", + "description": "find、grep、which、whereis 搜索命令", + "challenges": [ + {"id": "l5_1_find_name", "title": "按名称查找", "description": "在 /etc 下查找所有 .conf 文件", "hint": "find /etc -name '*.conf'", "success_test": "'.conf' in output", "solution": ["find /etc -name '*.conf'"], "success_msg": "🔍 文件已找到!"}, + {"id": "l5_2_find_type", "title": "按类型查找", "description": "查找 /tmp 下的所有目录", "hint": "find /tmp -type d", "success_test": "cmd.startswith('find') and '-type d' in cmd", "solution": ["find /tmp -type d"], "success_msg": "📁 目录已找到!"}, + {"id": "l5_3_find_size", "title": "按大小查找", "description": "查找 /var/log 下大于 1M 的文件", "hint": "find /var/log -size +1M", "success_test": "cmd.startswith('find') and '-size' in cmd", "solution": ["find /var/log -size +1M"], "success_msg": "📏 大文件已找到!"}, + {"id": "l5_4_find_exec", "title": "执行操作", "description": "查找 /tmp 下所有 .log 文件并删除", "hint": "find /tmp -name '*.log' -exec rm {} \\;", "success_test": "'-exec' in cmd", "solution": ["find /tmp -name '*.log' -exec rm {} \\;"], "success_msg": "🗑️ 操作执行成功!"}, + {"id": "l5_5_grep", "title": "文本搜索", "description": "在 /etc/passwd 中搜索 root 用户", "hint": "grep root /etc/passwd", "success_test": "'root' in output", "solution": ["grep root /etc/passwd"], "success_msg": "🎯 搜索成功!"}, + {"id": "l5_6_grep_i", "title": "忽略大小写", "description": "使用 grep -i 搜索 ROOT(不区分大小写)", "hint": "grep -i root /etc/passwd", "success_test": "'-i' in cmd", "solution": ["grep -i root /etc/passwd"], "success_msg": "🔤 忽略大小写搜索成功!"}, + {"id": "l5_7_grep_v", "title": "反向匹配", "description": "显示 /etc/passwd 中不包含 nologin 的行", "hint": "grep -v nologin /etc/passwd", "success_test": "'-v' in cmd", "solution": ["grep -v nologin /etc/passwd"], "success_msg": "🔄 反向匹配成功!"}, + {"id": "l5_8_grep_n", "title": "显示行号", "description": "使用 grep -n 显示匹配行的行号", "hint": "grep -n root /etc/passwd", "success_test": "'-n' in cmd and ':' in output", "solution": ["grep -n root /etc/passwd"], "success_msg": "🔢 行号已显示!"}, + {"id": "l5_9_which", "title": "查找命令位置", "description": "查找 ls 命令的位置", "hint": "which ls", "success_test": "'/bin' in output or '/usr' in output", "solution": ["which ls"], "success_msg": "📍 命令位置已找到!"}, + {"id": "l5_10_whereis", "title": "查找相关文件", "description": "使用 whereis 查找 ls 命令的相关文件", "hint": "whereis ls", "success_test": "cmd.startswith('whereis')", "solution": ["whereis ls"], "success_msg": "📚 相关文件已找到!"} + ] + }, + { + "id": "level_6_user", + "title": "👥 Level 6: 用户和组管理", + "description": "useradd、usermod、passwd、group 管理", + "challenges": [ + {"id": "l6_1_whoami", "title": "查看当前用户", "description": "使用 whoami 查看当前登录用户名", "hint": "whoami", "success_test": "len(output.strip()) > 0", "solution": ["whoami"], "success_msg": "👤 当前用户已显示!"}, + {"id": "l6_2_id", "title": "查看用户ID", "description": "使用 id 查看当前用户的 UID、GID 和所属组", "hint": "id", "success_test": "'uid' in output.lower()", "solution": ["id"], "success_msg": "🆔 用户信息已显示!"}, + {"id": "l6_3_w", "title": "查看登录用户", "description": "使用 w 查看当前登录系统的用户", "hint": "w", "success_test": "'USER' in output or 'TTY' in output", "solution": ["w"], "success_msg": "👥 登录用户已显示!"}, + {"id": "l6_4_last", "title": "查看登录历史", "description": "使用 last 查看最近的登录记录", "hint": "last", "success_test": "len(output) > 10", "solution": ["last"], "success_msg": "📜 登录历史已显示!"}, + {"id": "l6_5_cat_passwd", "title": "查看用户列表", "description": "查看 /etc/passwd 文件了解系统用户", "hint": "cat /etc/passwd", "success_test": "':' in output", "solution": ["cat /etc/passwd"], "success_msg": "📋 用户列表已显示!"}, + {"id": "l6_6_cat_group", "title": "查看组列表", "description": "查看 /etc/group 文件了解系统用户组", "hint": "cat /etc/group", "success_test": "':' in output", "solution": ["cat /etc/group"], "success_msg": "📋 组列表已显示!"}, + {"id": "l6_7_passwd", "title": "修改密码", "description": "使用 passwd 修改当前用户密码(输入当前密码和新密码)", "hint": "passwd", "success_test": "cmd == 'passwd'", "solution": ["passwd"], "success_msg": "🔑 密码修改命令已执行!"}, + {"id": "l6_8_su", "title": "切换用户", "description": "使用 su - 切换到 root 用户", "hint": "su -", "success_test": "cmd.startswith('su')", "solution": ["su -", "su"], "success_msg": "🔄 用户切换命令已执行!"} + ] + }, + { + "id": "level_7_disk", + "title": "💾 Level 7: 磁盘管理", + "description": "df、du、fdisk、mount 磁盘操作", + "challenges": [ + {"id": "l7_1_df", "title": "查看磁盘空间", "description": "使用 df 查看文件系统磁盘空间使用情况", "hint": "df", "success_test": "'Filesystem' in output or '文件系统' in output", "solution": ["df"], "success_msg": "💾 磁盘空间已显示!"}, + {"id": "l7_2_df_h", "title": "人类可读格式", "description": "使用 df -h 以人类可读格式显示磁盘空间", "hint": "df -h", "success_test": "'G' in output or 'M' in output or 'K' in output", "solution": ["df -h"], "success_msg": "📊 磁盘空间(易读格式)!"}, + {"id": "l7_3_du", "title": "查看目录大小", "description": "使用 du 查看 /tmp 目录的大小", "hint": "du /tmp", "success_test": "len(output) > 0", "solution": ["du /tmp"], "success_msg": "📏 目录大小已显示!"}, + {"id": "l7_4_du_sh", "title": "查看总大小", "description": "使用 du -sh 查看 /tmp 的总大小", "hint": "du -sh /tmp", "success_test": "'-sh' in cmd", "solution": ["du -sh /tmp"], "success_msg": "📊 总大小已显示!"}, + {"id": "l7_5_du_sort", "title": "排序查看", "description": "查看 /tmp 下最大的 5 个目录", "hint": "du -sh /tmp/* | sort -rh | head -5", "success_test": "'sort' in cmd", "solution": ["du -sh /tmp/* | sort -rh | head -5"], "success_msg": "🏆 最大目录已排序!"}, + {"id": "l7_6_mount", "title": "查看挂载点", "description": "使用 mount 查看已挂载的文件系统", "hint": "mount", "success_test": "'on' in output or 'type' in output", "solution": ["mount"], "success_msg": "🔗 挂载点已显示!"}, + {"id": "l7_7_fdisk", "title": "查看分区表", "description": "使用 fdisk -l 查看磁盘分区表(需要 root 权限)", "hint": "fdisk -l", "success_test": "cmd.startswith('fdisk')", "solution": ["fdisk -l"], "success_msg": "💿 分区表已显示!"} + ] + }, + { + "id": "level_8_process", + "title": "⚙️ Level 8: 进程管理", + "description": "ps、top、kill、nohup 进程控制", + "challenges": [ + {"id": "l8_1_ps", "title": "查看进程", "description": "使用 ps 查看当前用户的进程", "hint": "ps", "success_test": "'PID' in output", "solution": ["ps"], "success_msg": "📋 进程列表已显示!"}, + {"id": "l8_2_ps_aux", "title": "查看所有进程", "description": "使用 ps aux 查看系统所有进程", "hint": "ps aux", "success_test": "'%CPU' in output or 'RSS' in output", "solution": ["ps aux"], "success_msg": "📊 所有进程已显示!"}, + {"id": "l8_3_ps_grep", "title": "查找特定进程", "description": "查找包含 ssh 的进程", "hint": "ps aux | grep ssh", "success_test": "'|' in cmd", "solution": ["ps aux | grep ssh"], "success_msg": "🎯 进程已找到!"}, + {"id": "l8_4_top", "title": "实时进程监控", "description": "使用 top 实时查看进程(按 q 退出)", "hint": "top", "success_test": "cmd == 'top'", "solution": ["top"], "success_msg": "📈 实时监控已启动!"}, + {"id": "l8_5_kill", "title": "终止进程", "description": "使用 kill 命令(查看 PID 后终止)", "hint": "kill PID", "success_test": "cmd.startswith('kill')", "solution": ["kill 1234"], "success_msg": "💀 终止命令已执行!"}, + {"id": "l8_6_kill9", "title": "强制终止", "description": "使用 kill -9 强制终止进程", "hint": "kill -9 PID", "success_test": "'-9' in cmd", "solution": ["kill -9 1234"], "success_msg": "💥 强制终止已执行!"}, + {"id": "l8_7_pkill", "title": "按名称终止", "description": "使用 pkill 按进程名终止", "hint": "pkill process_name", "success_test": "cmd.startswith('pkill')", "solution": ["pkill python"], "success_msg": "🎯 按名称终止已执行!"}, + {"id": "l8_8_nohup", "title": "后台运行", "description": "使用 nohup 让命令在后台运行", "hint": "nohup command &", "success_test": "cmd.startswith('nohup')", "solution": ["nohup sleep 10 &"], "success_msg": "🌙 后台运行已设置!"} + ] + }, + { + "id": "level_9_network", + "title": "🌐 Level 9: 网络管理", + "description": "ifconfig、ping、netstat、curl 网络命令", + "challenges": [ + {"id": "l9_1_ifconfig", "title": "查看网卡", "description": "使用 ifconfig 查看网络接口配置", "hint": "ifconfig", "success_test": "'inet' in output or 'eth' in output or 'lo' in output", "solution": ["ifconfig"], "success_msg": "🎴 网卡信息已显示!"}, + {"id": "l9_2_ip_addr", "title": "现代方式查看", "description": "使用 ip addr 查看网络接口", "hint": "ip addr", "success_test": "cmd.startswith('ip addr')", "solution": ["ip addr"], "success_msg": "🎴 网络接口已显示!"}, + {"id": "l9_3_ping", "title": "测试连通性", "description": "ping 127.0.0.1 测试本地网络", "hint": "ping -c 4 127.0.0.1", "success_test": "cmd.startswith('ping')", "solution": ["ping -c 4 127.0.0.1"], "success_msg": "📡 Ping 测试完成!"}, + {"id": "l9_4_netstat", "title": "查看网络连接", "description": "使用 netstat -tlnp 查看监听端口", "hint": "netstat -tlnp", "success_test": "'LISTEN' in output or 'tcp' in output.lower()", "solution": ["netstat -tlnp"], "success_msg": "🔗 网络连接已显示!"}, + {"id": "l9_5_ss", "title": "现代方式查看端口", "description": "使用 ss -tlnp 查看监听端口", "hint": "ss -tlnp", "success_test": "cmd.startswith('ss')", "solution": ["ss -tlnp"], "success_msg": "🔗 端口信息已显示!"}, + {"id": "l9_6_curl", "title": "HTTP 请求", "description": "使用 curl 访问 http://localhost:8080", "hint": "curl http://localhost:8080", "success_test": "cmd.startswith('curl')", "solution": ["curl http://localhost:8080"], "success_msg": "🌐 HTTP 请求已发送!"}, + {"id": "l9_7_wget", "title": "下载文件", "description": "使用 wget 下载网页", "hint": "wget http://localhost:8080 -O /tmp/test.html", "success_test": "cmd.startswith('wget')", "solution": ["wget http://localhost:8080 -O /tmp/test.html"], "success_msg": "📥 文件下载命令已执行!"}, + {"id": "l9_8_traceroute", "title": "路由追踪", "description": "使用 traceroute 追踪到目标的路由", "hint": "traceroute 8.8.8.8", "success_test": "cmd.startswith('traceroute') or cmd.startswith('tracepath')", "solution": ["traceroute 8.8.8.8"], "success_msg": "🛤️ 路由追踪已启动!"} + ] + }, + { + "id": "level_10_package", + "title": "📦 Level 10: 软件包管理", + "description": "yum、apt、rpm、dpkg 包管理", + "challenges": [ + {"id": "l10_1_yum_list", "title": "列出已安装包", "description": "使用 yum list installed 查看已安装的包", "hint": "yum list installed", "success_test": "cmd.startswith('yum')", "solution": ["yum list installed"], "success_msg": "📋 已安装包列表!"}, + {"id": "l10_2_yum_search", "title": "搜索包", "description": "使用 yum search 搜索 nginx", "hint": "yum search nginx", "success_test": "cmd.startswith('yum search')", "solution": ["yum search nginx"], "success_msg": "🔍 包搜索完成!"}, + {"id": "l10_3_rpm_q", "title": "查询包信息", "description": "使用 rpm -q 查询 bash 包", "hint": "rpm -q bash", "success_test": "cmd.startswith('rpm')", "solution": ["rpm -q bash"], "success_msg": "📦 包信息已显示!"}, + {"id": "l10_4_apt_update", "title": "更新包列表", "description": "使用 apt update 更新包列表", "hint": "apt update", "success_test": "cmd.startswith('apt update')", "solution": ["apt update"], "success_msg": "🔄 包列表已更新!"}, + {"id": "l10_5_dpkg_l", "title": "列出 Debian 包", "description": "使用 dpkg -l 列出已安装的包", "hint": "dpkg -l", "success_test": "cmd.startswith('dpkg')", "solution": ["dpkg -l"], "success_msg": "📋 Debian 包列表!"} + ] + }, + { + "id": "level_11_vim", + "title": "✏️ Level 11: Vi/Vim 编辑器", + "description": "vim 基本操作和常用命令", + "challenges": [ + {"id": "l11_1_vim", "title": "打开文件", "description": "使用 vim 打开 /tmp/test.txt", "hint": "vim /tmp/test.txt", "success_test": "cmd.startswith('vim') or cmd.startswith('vi')", "solution": ["vim /tmp/test.txt"], "success_msg": "📝 Vim 已启动!"}, + {"id": "l11_2_vim_i", "title": "插入模式", "description": "在 vim 中按 i 进入插入模式", "hint": "按 i 键", "success_test": "cmd == 'i'", "solution": ["i"], "success_msg": "⌨️ 插入模式!"}, + {"id": "l11_3_vim_esc", "title": "退出插入模式", "description": "按 Esc 退出插入模式", "hint": "按 Esc 键", "success_test": "cmd == 'esc' or cmd == 'Esc'", "solution": ["esc"], "success_msg": "🚪 退出插入模式!"}, + {"id": "l11_4_vim_wq", "title": "保存退出", "description": "输入 :wq 保存并退出", "hint": ":wq", "success_test": "cmd == ':wq'", "solution": [":wq"], "success_msg": "💾 保存并退出!"}, + {"id": "l11_5_vim_q", "title": "不保存退出", "description": "输入 :q! 强制退出不保存", "hint": ":q!", "success_test": "cmd == ':q!'", "solution": [":q!"], "success_msg": "🚪 强制退出!"} + ] + }, + { + "id": "level_12_shell", + "title": "🚀 Level 12: Shell 脚本编程", + "description": "Shell 脚本基础、变量、循环、条件判断", + "challenges": [ + {"id": "l12_1_echo_var", "title": "输出变量", "description": "使用 echo 输出环境变量 $HOME", "hint": "echo $HOME", "success_test": "'$HOME' in cmd or 'echo $' in cmd", "solution": ["echo $HOME"], "success_msg": "🔤 变量已输出!"}, + {"id": "l12_2_env", "title": "查看环境变量", "description": "使用 env 查看所有环境变量", "hint": "env", "success_test": "cmd == 'env'", "solution": ["env"], "success_msg": "🌍 环境变量已显示!"}, + {"id": "l12_3_export", "title": "设置环境变量", "description": "使用 export 设置 MYVAR=test", "hint": "export MYVAR=test", "success_test": "cmd.startswith('export')", "solution": ["export MYVAR=test"], "success_msg": "📤 环境变量已设置!"}, + {"id": "l12_4_alias", "title": "命令别名", "description": "创建别名 ll='ls -l'", "hint": "alias ll='ls -l'", "success_test": "cmd.startswith('alias')", "solution": ["alias ll='ls -l'"], "success_msg": "🏷️ 别名已创建!"}, + {"id": "l12_5_date", "title": "显示日期", "description": "使用 date 显示当前日期时间", "hint": "date", "success_test": "cmd == 'date'", "solution": ["date"], "success_msg": "📅 日期已显示!"}, + {"id": "l12_6_cal", "title": "显示日历", "description": "使用 cal 显示本月日历", "hint": "cal", "success_test": "cmd == 'cal'", "solution": ["cal"], "success_msg": "📆 日历已显示!"}, + {"id": "l12_7_bc", "title": "计算器", "description": "使用 bc 计算 1+1", "hint": "echo '1+1' | bc", "success_test": "'bc' in cmd", "solution": ["echo '1+1' | bc"], "success_msg": "🧮 计算完成!"}, + {"id": "l12_8_tar", "title": "打包压缩", "description": "将 /tmp/testdir 打包为 /tmp/testdir.tar.gz", "hint": "tar -czf /tmp/testdir.tar.gz /tmp/testdir", "success_test": "cmd.startswith('tar')", "solution": ["tar -czf /tmp/testdir.tar.gz /tmp/testdir"], "success_msg": "📦 打包完成!"}, + {"id": "l12_9_untar", "title": "解压", "description": "解压 /tmp/testdir.tar.gz 到 /tmp/extract/", "hint": "tar -xzf /tmp/testdir.tar.gz -C /tmp/extract/", "success_test": "'-x' in cmd or '--extract' in cmd", "solution": ["tar -xzf /tmp/testdir.tar.gz -C /tmp/"], "success_msg": "📂 解压完成!"}, + {"id": "l12_10_crontab", "title": "定时任务", "description": "使用 crontab -l 查看定时任务", "hint": "crontab -l", "success_test": "cmd.startswith('crontab')", "solution": ["crontab -l"], "success_msg": "⏰ 定时任务已显示!"} + ] + } + ] +} diff --git a/COURSE_TASKS.json b/COURSE_TASKS.json new file mode 100644 index 0000000..1ec3d92 --- /dev/null +++ b/COURSE_TASKS.json @@ -0,0 +1,192 @@ +{ + "meta": { + "version": "3.0", + "title": "Linux 运维工程师完整教程", + "author": "PMClaw", + "updated": "2026-03-06", + "description": "覆盖菜鸟教程 Linux 全部内容,从入门到精通", + "total_levels": 12, + "total_challenges": 80 + }, + "levels": [ + { + "id": "level_1_basic", + "title": "🌱 Level 1: Linux 基础入门", + "description": "Linux 简介、目录结构、基本操作", + "challenges": [ + {"id": "l1_1_pwd", "title": "查看当前目录", "description": "使用 pwd 命令显示当前工作目录的完整路径", "hint": "直接输入 pwd", "success_test": "'/' in output", "solution": ["pwd"], "success_msg": "📍 定位成功!"}, + {"id": "l1_2_ls", "title": "列出目录内容", "description": "使用 ls 命令查看当前目录下的文件和文件夹", "hint": "输入 ls", "success_test": "len(output) > 0", "solution": ["ls"], "success_msg": "📂 目录内容已显示!"}, + {"id": "l1_3_ls_l", "title": "详细列表", "description": "使用 ls -l 显示详细信息,包括权限、所有者、大小等", "hint": "ls -l", "success_test": "'total' in output or '-' in output[:20]", "solution": ["ls -l"], "success_msg": "📋 详细信息已获取!"}, + {"id": "l1_4_ls_a", "title": "显示隐藏文件", "description": "使用 ls -a 显示包括隐藏文件在内的所有文件", "hint": "ls -a", "success_test": "'.' in output and '..' in output", "solution": ["ls -a"], "success_msg": "👻 隐藏文件已显示!"}, + {"id": "l1_5_cd", "title": "切换目录", "description": "使用 cd 命令进入 /tmp 目录", "hint": "cd /tmp", "success_test": "cwd == '/tmp'", "solution": ["cd /tmp"], "success_msg": "🚶 目录切换成功!"}, + {"id": "l1_6_cd_back", "title": "返回上级目录", "description": "使用 cd .. 返回上级目录", "hint": "cd ..", "success_test": "cwd != '/tmp'", "solution": ["cd .."], "success_msg": "⬆️ 返回成功!"}, + {"id": "l1_7_cd_home", "title": "返回用户主目录", "description": "使用 cd ~ 或 cd 返回用户主目录", "hint": "cd ~", "success_test": "'home' in cwd or cwd == '/'", "solution": ["cd ~", "cd"], "success_msg": "🏠 已回到家目录!"}, + {"id": "l1_8_clear", "title": "清屏", "description": "使用 clear 命令清屏", "hint": "clear", "success_test": "cmd == 'clear'", "solution": ["clear"], "success_msg": "🧹 屏幕已清空!"} + ] + }, + { + "id": "level_2_file", + "title": "📁 Level 2: 文件与目录管理", + "description": "创建、删除、复制、移动文件和目录", + "challenges": [ + {"id": "l2_1_mkdir", "title": "创建目录", "description": "使用 mkdir 创建 /tmp/testdir 目录", "hint": "mkdir /tmp/testdir", "success_test": "exists('/tmp/testdir')", "solution": ["mkdir /tmp/testdir"], "success_msg": "📂 目录创建成功!"}, + {"id": "l2_2_mkdir_p", "title": "递归创建目录", "description": "使用 mkdir -p 创建 /tmp/a/b/c 多级目录", "hint": "mkdir -p /tmp/a/b/c", "success_test": "exists('/tmp/a/b/c')", "solution": ["mkdir -p /tmp/a/b/c"], "success_msg": "📁 多级目录创建成功!"}, + {"id": "l2_3_touch", "title": "创建空文件", "description": "使用 touch 创建 /tmp/test.txt 文件", "hint": "touch /tmp/test.txt", "success_test": "exists('/tmp/test.txt')", "solution": ["touch /tmp/test.txt"], "success_msg": "📄 文件创建成功!"}, + {"id": "l2_4_cp", "title": "复制文件", "description": "将 /etc/hosts 复制到 /tmp/hosts.bak", "hint": "cp /etc/hosts /tmp/hosts.bak", "success_test": "exists('/tmp/hosts.bak')", "solution": ["cp /etc/hosts /tmp/hosts.bak"], "success_msg": "📋 文件复制成功!"}, + {"id": "l2_5_cp_r", "title": "复制目录", "description": "使用 cp -r 复制 /etc/skel 到 /tmp/skel_backup", "hint": "cp -r /etc/skel /tmp/skel_backup", "success_test": "exists('/tmp/skel_backup')", "solution": ["cp -r /etc/skel /tmp/skel_backup"], "success_msg": "📁 目录复制成功!"}, + {"id": "l2_6_mv", "title": "移动文件", "description": "将 /tmp/test.txt 移动到 /tmp/testdir/", "hint": "mv /tmp/test.txt /tmp/testdir/", "success_test": "exists('/tmp/testdir/test.txt')", "solution": ["mv /tmp/test.txt /tmp/testdir/"], "success_msg": "🚚 文件移动成功!"}, + {"id": "l2_7_mv_rename", "title": "重命名文件", "description": "将 /tmp/hosts.bak 重命名为 /tmp/hosts.backup", "hint": "mv /tmp/hosts.bak /tmp/hosts.backup", "success_test": "exists('/tmp/hosts.backup')", "solution": ["mv /tmp/hosts.bak /tmp/hosts.backup"], "success_msg": "✏️ 重命名成功!"}, + {"id": "l2_8_rm", "title": "删除文件", "description": "删除 /tmp/hosts.backup 文件", "hint": "rm /tmp/hosts.backup", "success_test": "not exists('/tmp/hosts.backup')", "solution": ["rm /tmp/hosts.backup"], "success_msg": "🗑️ 文件删除成功!"}, + {"id": "l2_9_rm_r", "title": "删除目录", "description": "使用 rm -r 删除 /tmp/skel_backup 目录", "hint": "rm -r /tmp/skel_backup", "success_test": "not exists('/tmp/skel_backup')", "solution": ["rm -r /tmp/skel_backup"], "success_msg": "🗑️ 目录删除成功!"}, + {"id": "l2_10_rm_rf", "title": "强制删除", "description": "使用 rm -rf 强制删除 /tmp/a 目录及其所有内容", "hint": "rm -rf /tmp/a", "success_test": "not exists('/tmp/a')", "solution": ["rm -rf /tmp/a"], "success_msg": "💥 强制删除成功!"} + ] + }, + { + "id": "level_3_view", + "title": "👁️ Level 3: 文件内容查看", + "description": "cat、more、less、head、tail 等查看命令", + "challenges": [ + {"id": "l3_1_cat", "title": "查看文件内容", "description": "使用 cat 查看 /etc/passwd 文件内容", "hint": "cat /etc/passwd", "success_test": "'root' in output", "solution": ["cat /etc/passwd"], "success_msg": "📖 文件内容已显示!"}, + {"id": "l3_2_cat_n", "title": "显示行号", "description": "使用 cat -n 显示 /etc/passwd 并带行号", "hint": "cat -n /etc/passwd", "success_test": "'1 ' in output or ' 1 ' in output", "solution": ["cat -n /etc/passwd"], "success_msg": "🔢 行号已显示!"}, + {"id": "l3_3_head", "title": "查看开头", "description": "使用 head 查看 /etc/passwd 前 10 行", "hint": "head /etc/passwd", "success_test": "len(output.split('\\n')) >= 5", "solution": ["head /etc/passwd"], "success_msg": "👆 开头内容已显示!"}, + {"id": "l3_4_head_n", "title": "指定行数", "description": "使用 head -n 5 查看前 5 行", "hint": "head -n 5 /etc/passwd", "success_test": "len(output.split('\\n')) <= 7", "solution": ["head -5 /etc/passwd", "head -n 5 /etc/passwd"], "success_msg": "📏 指定行数已显示!"}, + {"id": "l3_5_tail", "title": "查看结尾", "description": "使用 tail 查看 /etc/passwd 最后 10 行", "hint": "tail /etc/passwd", "success_test": "len(output) > 10", "solution": ["tail /etc/passwd"], "success_msg": "👇 结尾内容已显示!"}, + {"id": "l3_6_tail_f", "title": "实时追踪", "description": "使用 tail -f 实时查看 /var/log/syslog(按 Ctrl+C 退出)", "hint": "tail -f /var/log/syslog", "success_test": "cmd.startswith('tail -f')", "solution": ["tail -f /var/log/syslog"], "success_msg": "👀 实时追踪模式!"}, + {"id": "l3_7_more", "title": "分页查看", "description": "使用 more 分页查看 /etc/passwd", "hint": "more /etc/passwd", "success_test": "cmd.startswith('more')", "solution": ["more /etc/passwd"], "success_msg": "📄 分页查看模式!"}, + {"id": "l3_8_less", "title": "可滚动查看", "description": "使用 less 查看 /etc/passwd(支持上下滚动)", "hint": "less /etc/passwd", "success_test": "cmd.startswith('less')", "solution": ["less /etc/passwd"], "success_msg": "📜 可滚动查看模式!"} + ] + }, + { + "id": "level_4_permission", + "title": "🔐 Level 4: 文件权限管理", + "description": "chmod、chown、chgrp 权限控制", + "challenges": [ + {"id": "l4_1_ls_l", "title": "查看权限", "description": "使用 ls -l /etc/passwd 查看文件权限", "hint": "ls -l /etc/passwd", "success_test": "'-' in output or 'r' in output", "solution": ["ls -l /etc/passwd"], "success_msg": "👀 权限信息已显示!"}, + {"id": "l4_2_chmod_755", "title": "修改权限为755", "description": "将 /tmp/testdir 权限改为 rwxr-xr-x (755)", "hint": "chmod 755 /tmp/testdir", "success_test": "cmd == 'chmod 755 /tmp/testdir'", "solution": ["chmod 755 /tmp/testdir"], "success_msg": "🔐 权限修改成功!"}, + {"id": "l4_3_chmod_644", "title": "修改权限为644", "description": "将 /tmp/testdir/test.txt 权限改为 rw-r--r-- (644)", "hint": "chmod 644 /tmp/testdir/test.txt", "success_test": "cmd == 'chmod 644 /tmp/testdir/test.txt'", "solution": ["chmod 644 /tmp/testdir/test.txt"], "success_msg": "🔐 权限修改成功!"}, + {"id": "l4_4_chmod_x", "title": "添加执行权限", "description": "给 /tmp/testdir/test.txt 添加可执行权限", "hint": "chmod +x /tmp/testdir/test.txt", "success_test": "cmd == 'chmod +x /tmp/testdir/test.txt'", "solution": ["chmod +x /tmp/testdir/test.txt"], "success_msg": "⚡ 执行权限已添加!"}, + {"id": "l4_5_chmod_r", "title": "移除读权限", "description": "移除 /tmp/testdir/test.txt 的读权限", "hint": "chmod -r /tmp/testdir/test.txt", "success_test": "cmd == 'chmod -r /tmp/testdir/test.txt'", "solution": ["chmod -r /tmp/testdir/test.txt"], "success_msg": "🚫 读权限已移除!"}, + {"id": "l4_6_chown", "title": "修改所有者", "description": "将 /tmp/testdir/test.txt 所有者改为 root", "hint": "chown root /tmp/testdir/test.txt", "success_test": "cmd.startswith('chown')", "solution": ["chown root /tmp/testdir/test.txt"], "success_msg": "👤 所有者已修改!"}, + {"id": "l4_7_chgrp", "title": "修改所属组", "description": "将 /tmp/testdir/test.txt 所属组改为 root", "hint": "chgrp root /tmp/testdir/test.txt", "success_test": "cmd.startswith('chgrp')", "solution": ["chgrp root /tmp/testdir/test.txt"], "success_msg": "👥 所属组已修改!"}, + {"id": "l4_8_chown_r", "title": "递归修改", "description": "递归修改 /tmp/testdir 及其所有内容的所有者为 root", "hint": "chown -R root /tmp/testdir", "success_test": "cmd.startswith('chown -R')", "solution": ["chown -R root /tmp/testdir"], "success_msg": "🔄 递归修改成功!"} + ] + }, + { + "id": "level_5_search", + "title": "🔍 Level 5: 搜索与过滤", + "description": "find、grep、which、whereis 搜索命令", + "challenges": [ + {"id": "l5_1_find_name", "title": "按名称查找", "description": "在 /etc 下查找所有 .conf 文件", "hint": "find /etc -name '*.conf'", "success_test": "'.conf' in output", "solution": ["find /etc -name '*.conf'"], "success_msg": "🔍 文件已找到!"}, + {"id": "l5_2_find_type", "title": "按类型查找", "description": "查找 /tmp 下的所有目录", "hint": "find /tmp -type d", "success_test": "cmd.startswith('find') and '-type d' in cmd", "solution": ["find /tmp -type d"], "success_msg": "📁 目录已找到!"}, + {"id": "l5_3_find_size", "title": "按大小查找", "description": "查找 /var/log 下大于 1M 的文件", "hint": "find /var/log -size +1M", "success_test": "cmd.startswith('find') and '-size' in cmd", "solution": ["find /var/log -size +1M"], "success_msg": "📏 大文件已找到!"}, + {"id": "l5_4_find_exec", "title": "执行操作", "description": "查找 /tmp 下所有 .log 文件并删除", "hint": "find /tmp -name '*.log' -exec rm {} \\;", "success_test": "'-exec' in cmd", "solution": ["find /tmp -name '*.log' -exec rm {} \\;"], "success_msg": "🗑️ 操作执行成功!"}, + {"id": "l5_5_grep", "title": "文本搜索", "description": "在 /etc/passwd 中搜索 root 用户", "hint": "grep root /etc/passwd", "success_test": "'root' in output", "solution": ["grep root /etc/passwd"], "success_msg": "🎯 搜索成功!"}, + {"id": "l5_6_grep_i", "title": "忽略大小写", "description": "使用 grep -i 搜索 ROOT(不区分大小写)", "hint": "grep -i root /etc/passwd", "success_test": "'-i' in cmd", "solution": ["grep -i root /etc/passwd"], "success_msg": "🔤 忽略大小写搜索成功!"}, + {"id": "l5_7_grep_v", "title": "反向匹配", "description": "显示 /etc/passwd 中不包含 nologin 的行", "hint": "grep -v nologin /etc/passwd", "success_test": "'-v' in cmd", "solution": ["grep -v nologin /etc/passwd"], "success_msg": "🔄 反向匹配成功!"}, + {"id": "l5_8_grep_n", "title": "显示行号", "description": "使用 grep -n 显示匹配行的行号", "hint": "grep -n root /etc/passwd", "success_test": "'-n' in cmd and ':' in output", "solution": ["grep -n root /etc/passwd"], "success_msg": "🔢 行号已显示!"}, + {"id": "l5_9_which", "title": "查找命令位置", "description": "查找 ls 命令的位置", "hint": "which ls", "success_test": "'/bin' in output or '/usr' in output", "solution": ["which ls"], "success_msg": "📍 命令位置已找到!"}, + {"id": "l5_10_whereis", "title": "查找相关文件", "description": "使用 whereis 查找 ls 命令的相关文件", "hint": "whereis ls", "success_test": "cmd.startswith('whereis')", "solution": ["whereis ls"], "success_msg": "📚 相关文件已找到!"} + ] + }, + { + "id": "level_6_user", + "title": "👥 Level 6: 用户和组管理", + "description": "useradd、usermod、passwd、group 管理", + "challenges": [ + {"id": "l6_1_whoami", "title": "查看当前用户", "description": "使用 whoami 查看当前登录用户名", "hint": "whoami", "success_test": "len(output.strip()) > 0", "solution": ["whoami"], "success_msg": "👤 当前用户已显示!"}, + {"id": "l6_2_id", "title": "查看用户ID", "description": "使用 id 查看当前用户的 UID、GID 和所属组", "hint": "id", "success_test": "'uid' in output.lower()", "solution": ["id"], "success_msg": "🆔 用户信息已显示!"}, + {"id": "l6_3_w", "title": "查看登录用户", "description": "使用 w 查看当前登录系统的用户", "hint": "w", "success_test": "'USER' in output or 'TTY' in output", "solution": ["w"], "success_msg": "👥 登录用户已显示!"}, + {"id": "l6_4_last", "title": "查看登录历史", "description": "使用 last 查看最近的登录记录", "hint": "last", "success_test": "len(output) > 10", "solution": ["last"], "success_msg": "📜 登录历史已显示!"}, + {"id": "l6_5_cat_passwd", "title": "查看用户列表", "description": "查看 /etc/passwd 文件了解系统用户", "hint": "cat /etc/passwd", "success_test": "':' in output", "solution": ["cat /etc/passwd"], "success_msg": "📋 用户列表已显示!"}, + {"id": "l6_6_cat_group", "title": "查看组列表", "description": "查看 /etc/group 文件了解系统用户组", "hint": "cat /etc/group", "success_test": "':' in output", "solution": ["cat /etc/group"], "success_msg": "📋 组列表已显示!"}, + {"id": "l6_7_passwd", "title": "修改密码", "description": "使用 passwd 修改当前用户密码(输入当前密码和新密码)", "hint": "passwd", "success_test": "cmd == 'passwd'", "solution": ["passwd"], "success_msg": "🔑 密码修改命令已执行!"}, + {"id": "l6_8_su", "title": "切换用户", "description": "使用 su - 切换到 root 用户", "hint": "su -", "success_test": "cmd.startswith('su')", "solution": ["su -", "su"], "success_msg": "🔄 用户切换命令已执行!"} + ] + }, + { + "id": "level_7_disk", + "title": "💾 Level 7: 磁盘管理", + "description": "df、du、fdisk、mount 磁盘操作", + "challenges": [ + {"id": "l7_1_df", "title": "查看磁盘空间", "description": "使用 df 查看文件系统磁盘空间使用情况", "hint": "df", "success_test": "'Filesystem' in output or '文件系统' in output", "solution": ["df"], "success_msg": "💾 磁盘空间已显示!"}, + {"id": "l7_2_df_h", "title": "人类可读格式", "description": "使用 df -h 以人类可读格式显示磁盘空间", "hint": "df -h", "success_test": "'G' in output or 'M' in output or 'K' in output", "solution": ["df -h"], "success_msg": "📊 磁盘空间(易读格式)!"}, + {"id": "l7_3_du", "title": "查看目录大小", "description": "使用 du 查看 /tmp 目录的大小", "hint": "du /tmp", "success_test": "len(output) > 0", "solution": ["du /tmp"], "success_msg": "📏 目录大小已显示!"}, + {"id": "l7_4_du_sh", "title": "查看总大小", "description": "使用 du -sh 查看 /tmp 的总大小", "hint": "du -sh /tmp", "success_test": "'-sh' in cmd", "solution": ["du -sh /tmp"], "success_msg": "📊 总大小已显示!"}, + {"id": "l7_5_du_sort", "title": "排序查看", "description": "查看 /tmp 下最大的 5 个目录", "hint": "du -sh /tmp/* | sort -rh | head -5", "success_test": "'sort' in cmd", "solution": ["du -sh /tmp/* | sort -rh | head -5"], "success_msg": "🏆 最大目录已排序!"}, + {"id": "l7_6_mount", "title": "查看挂载点", "description": "使用 mount 查看已挂载的文件系统", "hint": "mount", "success_test": "'on' in output or 'type' in output", "solution": ["mount"], "success_msg": "🔗 挂载点已显示!"}, + {"id": "l7_7_fdisk", "title": "查看分区表", "description": "使用 fdisk -l 查看磁盘分区表(需要 root 权限)", "hint": "fdisk -l", "success_test": "cmd.startswith('fdisk')", "solution": ["fdisk -l"], "success_msg": "💿 分区表已显示!"} + ] + }, + { + "id": "level_8_process", + "title": "⚙️ Level 8: 进程管理", + "description": "ps、top、kill、nohup 进程控制", + "challenges": [ + {"id": "l8_1_ps", "title": "查看进程", "description": "使用 ps 查看当前用户的进程", "hint": "ps", "success_test": "'PID' in output", "solution": ["ps"], "success_msg": "📋 进程列表已显示!"}, + {"id": "l8_2_ps_aux", "title": "查看所有进程", "description": "使用 ps aux 查看系统所有进程", "hint": "ps aux", "success_test": "'%CPU' in output or 'RSS' in output", "solution": ["ps aux"], "success_msg": "📊 所有进程已显示!"}, + {"id": "l8_3_ps_grep", "title": "查找特定进程", "description": "查找包含 ssh 的进程", "hint": "ps aux | grep ssh", "success_test": "'|' in cmd", "solution": ["ps aux | grep ssh"], "success_msg": "🎯 进程已找到!"}, + {"id": "l8_4_top", "title": "实时进程监控", "description": "使用 top 实时查看进程(按 q 退出)", "hint": "top", "success_test": "cmd == 'top'", "solution": ["top"], "success_msg": "📈 实时监控已启动!"}, + {"id": "l8_5_kill", "title": "终止进程", "description": "使用 kill 命令(查看 PID 后终止)", "hint": "kill PID", "success_test": "cmd.startswith('kill')", "solution": ["kill 1234"], "success_msg": "💀 终止命令已执行!"}, + {"id": "l8_6_kill9", "title": "强制终止", "description": "使用 kill -9 强制终止进程", "hint": "kill -9 PID", "success_test": "'-9' in cmd", "solution": ["kill -9 1234"], "success_msg": "💥 强制终止已执行!"}, + {"id": "l8_7_pkill", "title": "按名称终止", "description": "使用 pkill 按进程名终止", "hint": "pkill process_name", "success_test": "cmd.startswith('pkill')", "solution": ["pkill python"], "success_msg": "🎯 按名称终止已执行!"}, + {"id": "l8_8_nohup", "title": "后台运行", "description": "使用 nohup 让命令在后台运行", "hint": "nohup command &", "success_test": "cmd.startswith('nohup')", "solution": ["nohup sleep 10 &"], "success_msg": "🌙 后台运行已设置!"} + ] + }, + { + "id": "level_9_network", + "title": "🌐 Level 9: 网络管理", + "description": "ifconfig、ping、netstat、curl 网络命令", + "challenges": [ + {"id": "l9_1_ifconfig", "title": "查看网卡", "description": "使用 ifconfig 查看网络接口配置", "hint": "ifconfig", "success_test": "'inet' in output or 'eth' in output or 'lo' in output", "solution": ["ifconfig"], "success_msg": "🎴 网卡信息已显示!"}, + {"id": "l9_2_ip_addr", "title": "现代方式查看", "description": "使用 ip addr 查看网络接口", "hint": "ip addr", "success_test": "cmd.startswith('ip addr')", "solution": ["ip addr"], "success_msg": "🎴 网络接口已显示!"}, + {"id": "l9_3_ping", "title": "测试连通性", "description": "ping 127.0.0.1 测试本地网络", "hint": "ping -c 4 127.0.0.1", "success_test": "cmd.startswith('ping')", "solution": ["ping -c 4 127.0.0.1"], "success_msg": "📡 Ping 测试完成!"}, + {"id": "l9_4_netstat", "title": "查看网络连接", "description": "使用 netstat -tlnp 查看监听端口", "hint": "netstat -tlnp", "success_test": "'LISTEN' in output or 'tcp' in output.lower()", "solution": ["netstat -tlnp"], "success_msg": "🔗 网络连接已显示!"}, + {"id": "l9_5_ss", "title": "现代方式查看端口", "description": "使用 ss -tlnp 查看监听端口", "hint": "ss -tlnp", "success_test": "cmd.startswith('ss')", "solution": ["ss -tlnp"], "success_msg": "🔗 端口信息已显示!"}, + {"id": "l9_6_curl", "title": "HTTP 请求", "description": "使用 curl 访问 http://localhost:8080", "hint": "curl http://localhost:8080", "success_test": "cmd.startswith('curl')", "solution": ["curl http://localhost:8080"], "success_msg": "🌐 HTTP 请求已发送!"}, + {"id": "l9_7_wget", "title": "下载文件", "description": "使用 wget 下载网页", "hint": "wget http://localhost:8080 -O /tmp/test.html", "success_test": "cmd.startswith('wget')", "solution": ["wget http://localhost:8080 -O /tmp/test.html"], "success_msg": "📥 文件下载命令已执行!"}, + {"id": "l9_8_traceroute", "title": "路由追踪", "description": "使用 traceroute 追踪到目标的路由", "hint": "traceroute 8.8.8.8", "success_test": "cmd.startswith('traceroute') or cmd.startswith('tracepath')", "solution": ["traceroute 8.8.8.8"], "success_msg": "🛤️ 路由追踪已启动!"} + ] + }, + { + "id": "level_10_package", + "title": "📦 Level 10: 软件包管理", + "description": "yum、apt、rpm、dpkg 包管理", + "challenges": [ + {"id": "l10_1_yum_list", "title": "列出已安装包", "description": "使用 yum list installed 查看已安装的包", "hint": "yum list installed", "success_test": "cmd.startswith('yum')", "solution": ["yum list installed"], "success_msg": "📋 已安装包列表!"}, + {"id": "l10_2_yum_search", "title": "搜索包", "description": "使用 yum search 搜索 nginx", "hint": "yum search nginx", "success_test": "cmd.startswith('yum search')", "solution": ["yum search nginx"], "success_msg": "🔍 包搜索完成!"}, + {"id": "l10_3_rpm_q", "title": "查询包信息", "description": "使用 rpm -q 查询 bash 包", "hint": "rpm -q bash", "success_test": "cmd.startswith('rpm')", "solution": ["rpm -q bash"], "success_msg": "📦 包信息已显示!"}, + {"id": "l10_4_apt_update", "title": "更新包列表", "description": "使用 apt update 更新包列表", "hint": "apt update", "success_test": "cmd.startswith('apt update')", "solution": ["apt update"], "success_msg": "🔄 包列表已更新!"}, + {"id": "l10_5_dpkg_l", "title": "列出 Debian 包", "description": "使用 dpkg -l 列出已安装的包", "hint": "dpkg -l", "success_test": "cmd.startswith('dpkg')", "solution": ["dpkg -l"], "success_msg": "📋 Debian 包列表!"} + ] + }, + { + "id": "level_11_vim", + "title": "✏️ Level 11: Vi/Vim 编辑器", + "description": "vim 基本操作和常用命令", + "challenges": [ + {"id": "l11_1_vim", "title": "打开文件", "description": "使用 vim 打开 /tmp/test.txt", "hint": "vim /tmp/test.txt", "success_test": "cmd.startswith('vim') or cmd.startswith('vi')", "solution": ["vim /tmp/test.txt"], "success_msg": "📝 Vim 已启动!"}, + {"id": "l11_2_vim_i", "title": "插入模式", "description": "在 vim 中按 i 进入插入模式", "hint": "按 i 键", "success_test": "cmd == 'i'", "solution": ["i"], "success_msg": "⌨️ 插入模式!"}, + {"id": "l11_3_vim_esc", "title": "退出插入模式", "description": "按 Esc 退出插入模式", "hint": "按 Esc 键", "success_test": "cmd == 'esc' or cmd == 'Esc'", "solution": ["esc"], "success_msg": "🚪 退出插入模式!"}, + {"id": "l11_4_vim_wq", "title": "保存退出", "description": "输入 :wq 保存并退出", "hint": ":wq", "success_test": "cmd == ':wq'", "solution": [":wq"], "success_msg": "💾 保存并退出!"}, + {"id": "l11_5_vim_q", "title": "不保存退出", "description": "输入 :q! 强制退出不保存", "hint": ":q!", "success_test": "cmd == ':q!'", "solution": [":q!"], "success_msg": "🚪 强制退出!"} + ] + }, + { + "id": "level_12_shell", + "title": "🚀 Level 12: Shell 脚本编程", + "description": "Shell 脚本基础、变量、循环、条件判断", + "challenges": [ + {"id": "l12_1_echo_var", "title": "输出变量", "description": "使用 echo 输出环境变量 $HOME", "hint": "echo $HOME", "success_test": "'$HOME' in cmd or 'echo $' in cmd", "solution": ["echo $HOME"], "success_msg": "🔤 变量已输出!"}, + {"id": "l12_2_env", "title": "查看环境变量", "description": "使用 env 查看所有环境变量", "hint": "env", "success_test": "cmd == 'env'", "solution": ["env"], "success_msg": "🌍 环境变量已显示!"}, + {"id": "l12_3_export", "title": "设置环境变量", "description": "使用 export 设置 MYVAR=test", "hint": "export MYVAR=test", "success_test": "cmd.startswith('export')", "solution": ["export MYVAR=test"], "success_msg": "📤 环境变量已设置!"}, + {"id": "l12_4_alias", "title": "命令别名", "description": "创建别名 ll='ls -l'", "hint": "alias ll='ls -l'", "success_test": "cmd.startswith('alias')", "solution": ["alias ll='ls -l'"], "success_msg": "🏷️ 别名已创建!"}, + {"id": "l12_5_date", "title": "显示日期", "description": "使用 date 显示当前日期时间", "hint": "date", "success_test": "cmd == 'date'", "solution": ["date"], "success_msg": "📅 日期已显示!"}, + {"id": "l12_6_cal", "title": "显示日历", "description": "使用 cal 显示本月日历", "hint": "cal", "success_test": "cmd == 'cal'", "solution": ["cal"], "success_msg": "📆 日历已显示!"}, + {"id": "l12_7_bc", "title": "计算器", "description": "使用 bc 计算 1+1", "hint": "echo '1+1' | bc", "success_test": "'bc' in cmd", "solution": ["echo '1+1' | bc"], "success_msg": "🧮 计算完成!"}, + {"id": "l12_8_tar", "title": "打包压缩", "description": "将 /tmp/testdir 打包为 /tmp/testdir.tar.gz", "hint": "tar -czf /tmp/testdir.tar.gz /tmp/testdir", "success_test": "cmd.startswith('tar')", "solution": ["tar -czf /tmp/testdir.tar.gz /tmp/testdir"], "success_msg": "📦 打包完成!"}, + {"id": "l12_9_untar", "title": "解压", "description": "解压 /tmp/testdir.tar.gz 到 /tmp/extract/", "hint": "tar -xzf /tmp/testdir.tar.gz -C /tmp/extract/", "success_test": "'-x' in cmd or '--extract' in cmd", "solution": ["tar -xzf /tmp/testdir.tar.gz -C /tmp/"], "success_msg": "📂 解压完成!"}, + {"id": "l12_10_crontab", "title": "定时任务", "description": "使用 crontab -l 查看定时任务", "hint": "crontab -l", "success_test": "cmd.startswith('crontab')", "solution": ["crontab -l"], "success_msg": "⏰ 定时任务已显示!"} + ] + } + ] +} diff --git a/COURSE_TASKS.json.backup.1772810579 b/COURSE_TASKS.json.backup.1772810579 new file mode 100644 index 0000000..b3b79a0 --- /dev/null +++ b/COURSE_TASKS.json.backup.1772810579 @@ -0,0 +1,121 @@ +{ + "meta": { + "version": "1.0", + "title": "Linux 命令学习课程 - 沙盒练习题", + "author": "Dev Agent", + "created": "2026-03-04" + }, + "levels": [ + { + "id": "level_1_intro", + "title": "入门篇:熟悉终端", + "description": "前5个命令,让你快速上手 Linux 终端", + "challenges": [ + { + "id": "pwd_1", + "title": "我在哪?", + "description": "使用 `pwd` 命令查看你当前所在的目录", + "hint": "直接输入 pwd 回车", + "success_test": "if output == '/' then pass", + "solution": ["pwd"], + "fail_message": "❌ 还不对!试试 `pwd` 命令" + }, + { + "id": "ls_1", + "title": "看看周围", + "description": "使用 `ls` 命令列出 `/sandbox` 下的所有子目录和文件", + "hint": "输入 ls /sandbox", + "success_test": "if output contains 'users' and 'projects' and 'logs' then pass", + "solution": ["ls /sandbox", "ls /"], + "fail_message": "❌ 还差一点!试试 `ls /sandbox` 或 `ls /`" + }, + { + "id": "cd_1", + "title": "进入用户区", + "description": "使用 `cd` 命令进入 /users 目录", + "hint": "先 cd 进去,再用 pwd 确认", + "success_test": "if cwd == '/users' then pass", + "solution": ["cd /users", "cd /"], + "fail_message": "❌ 还没到 /users!试试 `cd /users`" + }, + { + "id": "cat_1", + "title": "读取文件", + "description": "使用 `cat` 命令读取 /users/alice.txt 的内容", + "hint": "cat /users/alice.txt", + "success_test": "if output contains 'Alice' and 'Linux' then pass", + "solution": ["cat /users/alice.txt"], + "fail_message": "❌ 记得完整路径!试试 `cat /users/alice.txt`" + }, + { + "id": "echo_1", + "title": "打招呼", + "description": "使用 `echo` 打印一句话:Hello Linux!", + "hint": "echo 黑客996", + "success_test": "if output contains 'Hello Linux' then pass", + "solution": ["echo Hello Linux!", "echo 'Hello Linux!'"], + "fail_message": "❌ 别忘了空格!试试 `echo Hello Linux!`" + } + ] + }, + { + "id": "level_2_files", + "title": "文件操作", + "description": "创建、复制、移动、查看文件内容(只读沙盒)", + "challenges": [ + { + "id": "tail_1", + "title": "看文件末尾", + "description": "使用 `tail -n 3` 查看 /projects/backend/app.py 的最后 3 行", + "hint": "tail -n 3 /projects/backend/app.py", + "success_test": "if output contains 'main' then pass", + "solution": ["tail -n 3 /projects/backend/app.py"], + "fail_message": "❌ 记得 `-n` 参数!试试 `tail -n 3`" + }, + { + "id": "head_1", + "title": "看文件开头", + "description": "使用 `head -n 2` 查看 /logs/access.log 的前 2 行", + "hint": "head -n 2 /logs/access.log", + "success_test": "if output contains '2024-01-01' then pass", + "solution": ["head -n 2 /logs/access.log"], + "fail_message": "❌ 别忘了 `-n 2`!试试 `head -n 2`" + }, + { + "id": "mkdir_1", + "title": "创建目录", + "description": "使用 `mkdir` 在 /projects 下创建一个叫 mycode 的目录", + "hint": "mkdir /projects/mycode", + "success_test": "if output contains 'mycode' then pass", + "solution": ["mkdir /projects/mycode"], + "fail_message": "❌ 路径别写错!试试 `mkdir /projects/mycode`" + } + ] + }, + { + "id": "level_3_search", + "title": "搜索高手", + "description": "grep/fun/wc/sort 组合技", + "challenges": [ + { + "id": "grep_1", + "title": "查找关键词", + "description": "使用 `grep` 在 /users/alice.txt 中查找包含 'Linux' 的行", + "hint": "grep Linux /users/alice.txt", + "success_test": "if output contains 'Linux' then pass", + "solution": ["grep Linux /users/alice.txt"], + "fail_message": "❌ 格式是 `grep pattern file`!试试 `grep Linux`" + }, + { + "id": "wc_1", + "title": "统计行数", + "description": "统计 /projects/backend/app.py 的总行数", + "hint": "wc -l /projects/backend/app.py", + "success_test": "if output contains '4' then pass", + "solution": ["wc -l /projects/backend/app.py"], + "fail_message": "❌ `-l` 是行数参数!试试 `wc -l`" + } + ] + } + ] +} diff --git a/COURSE_TASKS.json.bak b/COURSE_TASKS.json.bak new file mode 100644 index 0000000..92c9f77 --- /dev/null +++ b/COURSE_TASKS.json.bak @@ -0,0 +1,314 @@ +{ + "meta": { + "version": "2.0", + "title": "Linux 运维工程师成长路径", + "author": "PMClaw", + "updated": "2026-03-06", + "description": "从新手到高手,系统学习 Linux 运维技能" + }, + "levels": [ + { + "id": "level_1_intro", + "title": "🌱 Level 1: 入门篇 - 熟悉终端", + "description": "掌握最基本的 5 个命令,快速上手 Linux 终端", + "challenges": [ + { + "id": "pwd_1", + "title": "我在哪?", + "desc": "使用 pwd 命令查看你当前所在的目录", + "hint": "直接输入 pwd 回车", + "success_test": "output.strip() == '/'", + "solution": ["pwd"], + "success_msg": "🎉 恭喜你!你学会了第一个 Linux 命令!" + }, + { + "id": "ls_1", + "title": "看看周围", + "desc": "使用 ls 命令列出 /sandbox 下的所有子目录和文件", + "hint": "输入 ls /sandbox", + "success_test": "'users' in output and 'projects' in output", + "solution": ["ls /sandbox", "ls /"], + "success_msg": "👀 很好!你能看到周围的文件了" + }, + { + "id": "cd_1", + "title": "进入用户区", + "desc": "使用 cd 命令进入 /users 目录", + "hint": "先 cd 进去,再用 pwd 确认", + "success_test": "cwd == '/users'", + "solution": ["cd /users"], + "success_msg": "🚶 移动成功!你学会了在目录间穿梭" + }, + { + "id": "cat_1", + "title": "读取文件", + "desc": "使用 cat 命令读取 /users/alice.txt 的内容", + "hint": "cat /users/alice.txt", + "success_test": "'Alice' in output and 'Linux' in output", + "solution": ["cat /users/alice.txt"], + "success_msg": "📖 阅读成功!文件内容已掌握" + }, + { + "id": "echo_1", + "title": "输出文字", + "desc": "使用 echo 命令输出 'Hello Linux'", + "hint": "echo Hello Linux", + "success_test": "'Hello Linux' in output", + "solution": ["echo Hello Linux"], + "success_msg": "📢 声音洪亮!echo 命令已掌握" + } + ] + }, + { + "id": "level_2_file", + "title": "📁 Level 2: 文件操作 - 管理文件系统", + "description": "学习创建、删除、复制、移动文件和目录", + "challenges": [ + { + "id": "mkdir_1", + "title": "创建目录", + "desc": "在 /tmp 下创建一个名为 'mydir' 的目录", + "hint": "mkdir /tmp/mydir", + "success_test": "exists('/tmp/mydir')", + "solution": ["mkdir /tmp/mydir"], + "success_msg": "📂 目录创建成功!" + }, + { + "id": "touch_1", + "title": "创建文件", + "desc": "在 /tmp/mydir 下创建一个名为 'test.txt' 的空文件", + "hint": "touch /tmp/mydir/test.txt", + "success_test": "exists('/tmp/mydir/test.txt')", + "solution": ["touch /tmp/mydir/test.txt"], + "success_msg": "📄 文件创建成功!" + }, + { + "id": "cp_1", + "title": "复制文件", + "desc": "将 /users/alice.txt 复制到 /tmp/mydir/", + "hint": "cp /users/alice.txt /tmp/mydir/", + "success_test": "exists('/tmp/mydir/alice.txt')", + "solution": ["cp /users/alice.txt /tmp/mydir/"], + "success_msg": "📋 复制成功!文件已备份" + }, + { + "id": "mv_1", + "title": "移动文件", + "desc": "将 /tmp/mydir/alice.txt 重命名为 bob.txt", + "hint": "mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt", + "success_test": "exists('/tmp/mydir/bob.txt') and not exists('/tmp/mydir/alice.txt')", + "solution": ["mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt"], + "success_msg": "🚚 移动成功!文件已改名" + }, + { + "id": "rm_1", + "title": "删除文件", + "desc": "删除 /tmp/mydir/test.txt 文件", + "hint": "rm /tmp/mydir/test.txt", + "success_test": "not exists('/tmp/mydir/test.txt')", + "solution": ["rm /tmp/mydir/test.txt"], + "success_msg": "🗑️ 删除成功!文件已清理" + }, + { + "id": "chmod_1", + "title": "修改权限", + "desc": "给 /tmp/mydir/bob.txt 添加可执行权限", + "hint": "chmod +x /tmp/mydir/bob.txt", + "success_test": "is_executable('/tmp/mydir/bob.txt')", + "solution": ["chmod +x /tmp/mydir/bob.txt"], + "success_msg": "🔐 权限修改成功!" + } + ] + }, + { + "id": "level_3_text", + "title": "🔍 Level 3: 文本处理 - 数据分析师", + "description": "掌握 grep、sed、awk 等强大的文本处理工具", + "challenges": [ + { + "id": "grep_1", + "title": "搜索文本", + "desc": "在 /var/log/syslog 中搜索包含 'error' 的行", + "hint": "grep error /var/log/syslog", + "success_test": "'error' in output.lower()", + "solution": ["grep error /var/log/syslog", "grep -i error /var/log/syslog"], + "success_msg": "🎯 搜索成功!找到了错误日志" + }, + { + "id": "head_1", + "title": "查看开头", + "desc": "查看 /var/log/syslog 的前 5 行", + "hint": "head -n 5 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 5", + "solution": ["head -5 /var/log/syslog", "head -n 5 /var/log/syslog"], + "success_msg": "👀 看到了开头!" + }, + { + "id": "tail_1", + "title": "查看结尾", + "desc": "查看 /var/log/syslog 的最后 3 行", + "hint": "tail -n 3 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 3", + "solution": ["tail -3 /var/log/syslog", "tail -n 3 /var/log/syslog"], + "success_msg": "🔚 看到了结尾!" + }, + { + "id": "wc_1", + "title": "统计行数", + "desc": "统计 /var/log/syslog 有多少行", + "hint": "wc -l /var/log/syslog", + "success_test": "output.strip().isdigit() or ' ' in output", + "solution": ["wc -l /var/log/syslog"], + "success_msg": "🔢 统计完成!" + }, + { + "id": "find_1", + "title": "查找文件", + "desc": "在 /sandbox 下查找所有 .txt 文件", + "hint": "find /sandbox -name '*.txt'", + "success_test": "'.txt' in output", + "solution": ["find /sandbox -name '*.txt'"], + "success_msg": "🔍 查找成功!" + } + ] + }, + { + "id": "level_4_system", + "title": "⚙️ Level 4: 系统管理 - 运维工程师", + "description": "学习进程管理、磁盘监控、系统服务控制", + "challenges": [ + { + "id": "ps_1", + "title": "查看进程", + "desc": "查看当前运行的所有进程", + "hint": "ps aux", + "success_test": "'PID' in output and 'COMMAND' in output", + "solution": ["ps aux", "ps -ef"], + "success_msg": "👥 进程列表已显示!" + }, + { + "id": "df_1", + "title": "磁盘空间", + "desc": "查看磁盘使用情况", + "hint": "df -h", + "success_test": "'Filesystem' in output and 'Size' in output", + "solution": ["df -h"], + "success_msg": "💾 磁盘信息已获取!" + }, + { + "id": "du_1", + "title": "目录大小", + "desc": "查看 /sandbox 目录的总大小", + "hint": "du -sh /sandbox", + "success_test": "'sandbox' in output or 'M' in output or 'K' in output", + "solution": ["du -sh /sandbox"], + "success_msg": "📊 大小统计完成!" + }, + { + "id": "free_1", + "title": "内存使用", + "desc": "查看内存使用情况", + "hint": "free -h", + "success_test": "'Mem' in output and 'total' in output.lower()", + "solution": ["free -h"], + "success_msg": "🧠 内存信息已获取!" + }, + { + "id": "uptime_1", + "title": "系统运行时间", + "desc": "查看系统已运行多久", + "hint": "uptime", + "success_test": "'load average' in output.lower() or 'day' in output or 'up' in output.lower()", + "solution": ["uptime"], + "success_msg": "⏱️ 运行时间已显示!" + } + ] + }, + { + "id": "level_5_network", + "title": "🌐 Level 5: 网络运维 - 网络管理员", + "description": "掌握网络诊断、连接监控、服务配置", + "challenges": [ + { + "id": "ping_1", + "title": "测试连通性", + "desc": "ping 127.0.0.1 测试本地网络", + "hint": "ping -c 3 127.0.0.1", + "success_test": "'127.0.0.1' in output or 'bytes' in output or 'icmp' in output.lower()", + "solution": ["ping -c 3 127.0.0.1"], + "success_msg": "📡 网络连通!" + }, + { + "id": "netstat_1", + "title": "查看端口", + "desc": "查看所有监听的端口", + "hint": "netstat -tlnp", + "success_test": "'LISTEN' in output or 'tcp' in output.lower()", + "solution": ["netstat -tlnp", "ss -tlnp"], + "success_msg": "🔌 端口列表已显示!" + }, + { + "id": "curl_1", + "title": "HTTP 请求", + "desc": "使用 curl 访问 http://localhost:8080", + "hint": "curl http://localhost:8080", + "success_test": "'html' in output.lower() or 'http' in output.lower() or len(output) > 10", + "solution": ["curl http://localhost:8080", "curl -I http://localhost:8080"], + "success_msg": "🌐 HTTP 请求成功!" + }, + { + "id": "ifconfig_1", + "title": "查看网卡", + "desc": "查看网络接口配置", + "hint": "ifconfig 或 ip addr", + "success_test": "'inet' in output or 'eth0' in output or 'lo' in output", + "solution": ["ifconfig", "ip addr"], + "success_msg": "🎴 网卡信息已获取!" + } + ] + }, + { + "id": "level_6_advanced", + "title": "🚀 Level 6: 高级运维 - 架构师", + "description": "Shell 脚本、日志分析、性能优化", + "challenges": [ + { + "id": "pipe_1", + "title": "管道操作", + "desc": "使用管道 | 将 ps aux 的输出传给 grep 搜索 ssh", + "hint": "ps aux | grep ssh", + "success_test": "'|' in cmd and ('ssh' in output or 'grep' in output)", + "solution": ["ps aux | grep ssh"], + "success_msg": "🔀 管道操作成功!" + }, + { + "id": "redirect_1", + "title": "重定向输出", + "desc": "将 echo 'test' 的输出重定向到 /tmp/test.txt", + "hint": "echo 'test' > /tmp/test.txt", + "success_test": "'>' in cmd and exists('/tmp/test.txt')", + "solution": ["echo 'test' > /tmp/test.txt"], + "success_msg": "📤 重定向成功!" + }, + { + "id": "tar_1", + "title": "压缩文件", + "desc": "将 /sandbox 目录打包压缩为 /tmp/sandbox.tar.gz", + "hint": "tar -czf /tmp/sandbox.tar.gz /sandbox", + "success_test": "exists('/tmp/sandbox.tar.gz')", + "solution": ["tar -czf /tmp/sandbox.tar.gz /sandbox"], + "success_msg": "📦 压缩成功!" + }, + { + "id": "cron_1", + "title": "定时任务", + "desc": "查看当前用户的定时任务", + "hint": "crontab -l", + "success_test": "'crontab' in cmd", + "solution": ["crontab -l"], + "success_msg": "⏰ 定时任务已查看!" + } + ] + } + ] +} diff --git a/COURSE_TASKS_NEW.json b/COURSE_TASKS_NEW.json new file mode 100644 index 0000000..92c9f77 --- /dev/null +++ b/COURSE_TASKS_NEW.json @@ -0,0 +1,314 @@ +{ + "meta": { + "version": "2.0", + "title": "Linux 运维工程师成长路径", + "author": "PMClaw", + "updated": "2026-03-06", + "description": "从新手到高手,系统学习 Linux 运维技能" + }, + "levels": [ + { + "id": "level_1_intro", + "title": "🌱 Level 1: 入门篇 - 熟悉终端", + "description": "掌握最基本的 5 个命令,快速上手 Linux 终端", + "challenges": [ + { + "id": "pwd_1", + "title": "我在哪?", + "desc": "使用 pwd 命令查看你当前所在的目录", + "hint": "直接输入 pwd 回车", + "success_test": "output.strip() == '/'", + "solution": ["pwd"], + "success_msg": "🎉 恭喜你!你学会了第一个 Linux 命令!" + }, + { + "id": "ls_1", + "title": "看看周围", + "desc": "使用 ls 命令列出 /sandbox 下的所有子目录和文件", + "hint": "输入 ls /sandbox", + "success_test": "'users' in output and 'projects' in output", + "solution": ["ls /sandbox", "ls /"], + "success_msg": "👀 很好!你能看到周围的文件了" + }, + { + "id": "cd_1", + "title": "进入用户区", + "desc": "使用 cd 命令进入 /users 目录", + "hint": "先 cd 进去,再用 pwd 确认", + "success_test": "cwd == '/users'", + "solution": ["cd /users"], + "success_msg": "🚶 移动成功!你学会了在目录间穿梭" + }, + { + "id": "cat_1", + "title": "读取文件", + "desc": "使用 cat 命令读取 /users/alice.txt 的内容", + "hint": "cat /users/alice.txt", + "success_test": "'Alice' in output and 'Linux' in output", + "solution": ["cat /users/alice.txt"], + "success_msg": "📖 阅读成功!文件内容已掌握" + }, + { + "id": "echo_1", + "title": "输出文字", + "desc": "使用 echo 命令输出 'Hello Linux'", + "hint": "echo Hello Linux", + "success_test": "'Hello Linux' in output", + "solution": ["echo Hello Linux"], + "success_msg": "📢 声音洪亮!echo 命令已掌握" + } + ] + }, + { + "id": "level_2_file", + "title": "📁 Level 2: 文件操作 - 管理文件系统", + "description": "学习创建、删除、复制、移动文件和目录", + "challenges": [ + { + "id": "mkdir_1", + "title": "创建目录", + "desc": "在 /tmp 下创建一个名为 'mydir' 的目录", + "hint": "mkdir /tmp/mydir", + "success_test": "exists('/tmp/mydir')", + "solution": ["mkdir /tmp/mydir"], + "success_msg": "📂 目录创建成功!" + }, + { + "id": "touch_1", + "title": "创建文件", + "desc": "在 /tmp/mydir 下创建一个名为 'test.txt' 的空文件", + "hint": "touch /tmp/mydir/test.txt", + "success_test": "exists('/tmp/mydir/test.txt')", + "solution": ["touch /tmp/mydir/test.txt"], + "success_msg": "📄 文件创建成功!" + }, + { + "id": "cp_1", + "title": "复制文件", + "desc": "将 /users/alice.txt 复制到 /tmp/mydir/", + "hint": "cp /users/alice.txt /tmp/mydir/", + "success_test": "exists('/tmp/mydir/alice.txt')", + "solution": ["cp /users/alice.txt /tmp/mydir/"], + "success_msg": "📋 复制成功!文件已备份" + }, + { + "id": "mv_1", + "title": "移动文件", + "desc": "将 /tmp/mydir/alice.txt 重命名为 bob.txt", + "hint": "mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt", + "success_test": "exists('/tmp/mydir/bob.txt') and not exists('/tmp/mydir/alice.txt')", + "solution": ["mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt"], + "success_msg": "🚚 移动成功!文件已改名" + }, + { + "id": "rm_1", + "title": "删除文件", + "desc": "删除 /tmp/mydir/test.txt 文件", + "hint": "rm /tmp/mydir/test.txt", + "success_test": "not exists('/tmp/mydir/test.txt')", + "solution": ["rm /tmp/mydir/test.txt"], + "success_msg": "🗑️ 删除成功!文件已清理" + }, + { + "id": "chmod_1", + "title": "修改权限", + "desc": "给 /tmp/mydir/bob.txt 添加可执行权限", + "hint": "chmod +x /tmp/mydir/bob.txt", + "success_test": "is_executable('/tmp/mydir/bob.txt')", + "solution": ["chmod +x /tmp/mydir/bob.txt"], + "success_msg": "🔐 权限修改成功!" + } + ] + }, + { + "id": "level_3_text", + "title": "🔍 Level 3: 文本处理 - 数据分析师", + "description": "掌握 grep、sed、awk 等强大的文本处理工具", + "challenges": [ + { + "id": "grep_1", + "title": "搜索文本", + "desc": "在 /var/log/syslog 中搜索包含 'error' 的行", + "hint": "grep error /var/log/syslog", + "success_test": "'error' in output.lower()", + "solution": ["grep error /var/log/syslog", "grep -i error /var/log/syslog"], + "success_msg": "🎯 搜索成功!找到了错误日志" + }, + { + "id": "head_1", + "title": "查看开头", + "desc": "查看 /var/log/syslog 的前 5 行", + "hint": "head -n 5 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 5", + "solution": ["head -5 /var/log/syslog", "head -n 5 /var/log/syslog"], + "success_msg": "👀 看到了开头!" + }, + { + "id": "tail_1", + "title": "查看结尾", + "desc": "查看 /var/log/syslog 的最后 3 行", + "hint": "tail -n 3 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 3", + "solution": ["tail -3 /var/log/syslog", "tail -n 3 /var/log/syslog"], + "success_msg": "🔚 看到了结尾!" + }, + { + "id": "wc_1", + "title": "统计行数", + "desc": "统计 /var/log/syslog 有多少行", + "hint": "wc -l /var/log/syslog", + "success_test": "output.strip().isdigit() or ' ' in output", + "solution": ["wc -l /var/log/syslog"], + "success_msg": "🔢 统计完成!" + }, + { + "id": "find_1", + "title": "查找文件", + "desc": "在 /sandbox 下查找所有 .txt 文件", + "hint": "find /sandbox -name '*.txt'", + "success_test": "'.txt' in output", + "solution": ["find /sandbox -name '*.txt'"], + "success_msg": "🔍 查找成功!" + } + ] + }, + { + "id": "level_4_system", + "title": "⚙️ Level 4: 系统管理 - 运维工程师", + "description": "学习进程管理、磁盘监控、系统服务控制", + "challenges": [ + { + "id": "ps_1", + "title": "查看进程", + "desc": "查看当前运行的所有进程", + "hint": "ps aux", + "success_test": "'PID' in output and 'COMMAND' in output", + "solution": ["ps aux", "ps -ef"], + "success_msg": "👥 进程列表已显示!" + }, + { + "id": "df_1", + "title": "磁盘空间", + "desc": "查看磁盘使用情况", + "hint": "df -h", + "success_test": "'Filesystem' in output and 'Size' in output", + "solution": ["df -h"], + "success_msg": "💾 磁盘信息已获取!" + }, + { + "id": "du_1", + "title": "目录大小", + "desc": "查看 /sandbox 目录的总大小", + "hint": "du -sh /sandbox", + "success_test": "'sandbox' in output or 'M' in output or 'K' in output", + "solution": ["du -sh /sandbox"], + "success_msg": "📊 大小统计完成!" + }, + { + "id": "free_1", + "title": "内存使用", + "desc": "查看内存使用情况", + "hint": "free -h", + "success_test": "'Mem' in output and 'total' in output.lower()", + "solution": ["free -h"], + "success_msg": "🧠 内存信息已获取!" + }, + { + "id": "uptime_1", + "title": "系统运行时间", + "desc": "查看系统已运行多久", + "hint": "uptime", + "success_test": "'load average' in output.lower() or 'day' in output or 'up' in output.lower()", + "solution": ["uptime"], + "success_msg": "⏱️ 运行时间已显示!" + } + ] + }, + { + "id": "level_5_network", + "title": "🌐 Level 5: 网络运维 - 网络管理员", + "description": "掌握网络诊断、连接监控、服务配置", + "challenges": [ + { + "id": "ping_1", + "title": "测试连通性", + "desc": "ping 127.0.0.1 测试本地网络", + "hint": "ping -c 3 127.0.0.1", + "success_test": "'127.0.0.1' in output or 'bytes' in output or 'icmp' in output.lower()", + "solution": ["ping -c 3 127.0.0.1"], + "success_msg": "📡 网络连通!" + }, + { + "id": "netstat_1", + "title": "查看端口", + "desc": "查看所有监听的端口", + "hint": "netstat -tlnp", + "success_test": "'LISTEN' in output or 'tcp' in output.lower()", + "solution": ["netstat -tlnp", "ss -tlnp"], + "success_msg": "🔌 端口列表已显示!" + }, + { + "id": "curl_1", + "title": "HTTP 请求", + "desc": "使用 curl 访问 http://localhost:8080", + "hint": "curl http://localhost:8080", + "success_test": "'html' in output.lower() or 'http' in output.lower() or len(output) > 10", + "solution": ["curl http://localhost:8080", "curl -I http://localhost:8080"], + "success_msg": "🌐 HTTP 请求成功!" + }, + { + "id": "ifconfig_1", + "title": "查看网卡", + "desc": "查看网络接口配置", + "hint": "ifconfig 或 ip addr", + "success_test": "'inet' in output or 'eth0' in output or 'lo' in output", + "solution": ["ifconfig", "ip addr"], + "success_msg": "🎴 网卡信息已获取!" + } + ] + }, + { + "id": "level_6_advanced", + "title": "🚀 Level 6: 高级运维 - 架构师", + "description": "Shell 脚本、日志分析、性能优化", + "challenges": [ + { + "id": "pipe_1", + "title": "管道操作", + "desc": "使用管道 | 将 ps aux 的输出传给 grep 搜索 ssh", + "hint": "ps aux | grep ssh", + "success_test": "'|' in cmd and ('ssh' in output or 'grep' in output)", + "solution": ["ps aux | grep ssh"], + "success_msg": "🔀 管道操作成功!" + }, + { + "id": "redirect_1", + "title": "重定向输出", + "desc": "将 echo 'test' 的输出重定向到 /tmp/test.txt", + "hint": "echo 'test' > /tmp/test.txt", + "success_test": "'>' in cmd and exists('/tmp/test.txt')", + "solution": ["echo 'test' > /tmp/test.txt"], + "success_msg": "📤 重定向成功!" + }, + { + "id": "tar_1", + "title": "压缩文件", + "desc": "将 /sandbox 目录打包压缩为 /tmp/sandbox.tar.gz", + "hint": "tar -czf /tmp/sandbox.tar.gz /sandbox", + "success_test": "exists('/tmp/sandbox.tar.gz')", + "solution": ["tar -czf /tmp/sandbox.tar.gz /sandbox"], + "success_msg": "📦 压缩成功!" + }, + { + "id": "cron_1", + "title": "定时任务", + "desc": "查看当前用户的定时任务", + "hint": "crontab -l", + "success_test": "'crontab' in cmd", + "solution": ["crontab -l"], + "success_msg": "⏰ 定时任务已查看!" + } + ] + } + ] +} diff --git a/COURSE_TASKS_v2.json b/COURSE_TASKS_v2.json new file mode 100644 index 0000000..25127d2 --- /dev/null +++ b/COURSE_TASKS_v2.json @@ -0,0 +1,314 @@ +{ + "meta": { + "version": "2.0", + "title": "Linux 运维工程师成长路径", + "author": "PMClaw", + "updated": "2026-03-06", + "description": "从新手到高手,系统学习 Linux 运维技能" + }, + "levels": [ + { + "id": "level_1_intro", + "title": "🌱 Level 1: 入门篇 - 熟悉终端", + "description": "掌握最基本的 5 个命令,快速上手 Linux 终端", + "challenges": [ + { + "id": "pwd_1", + "title": "我在哪?", + "description": "使用 pwd 命令查看你当前所在的目录", + "hint": "直接输入 pwd 回车", + "success_test": "output.strip() == '/'", + "solution": ["pwd"], + "success_msg": "🎉 恭喜你!你学会了第一个 Linux 命令!" + }, + { + "id": "ls_1", + "title": "看看周围", + "description": "使用 ls 命令列出 /sandbox 下的所有子目录和文件", + "hint": "输入 ls /sandbox", + "success_test": "'users' in output and 'projects' in output", + "solution": ["ls /sandbox", "ls /"], + "success_msg": "👀 很好!你能看到周围的文件了" + }, + { + "id": "cd_1", + "title": "进入用户区", + "description": "使用 cd 命令进入 /users 目录", + "hint": "先 cd 进去,再用 pwd 确认", + "success_test": "cwd == '/users'", + "solution": ["cd /users"], + "success_msg": "🚶 移动成功!你学会了在目录间穿梭" + }, + { + "id": "cat_1", + "title": "读取文件", + "description": "使用 cat 命令读取 /users/alice.txt 的内容", + "hint": "cat /users/alice.txt", + "success_test": "'Alice' in output and 'Linux' in output", + "solution": ["cat /users/alice.txt"], + "success_msg": "📖 阅读成功!文件内容已掌握" + }, + { + "id": "echo_1", + "title": "输出文字", + "description": "使用 echo 命令输出 'Hello Linux'", + "hint": "echo Hello Linux", + "success_test": "'Hello Linux' in output", + "solution": ["echo Hello Linux"], + "success_msg": "📢 声音洪亮!echo 命令已掌握" + } + ] + }, + { + "id": "level_2_file", + "title": "📁 Level 2: 文件操作 - 管理文件系统", + "description": "学习创建、删除、复制、移动文件和目录", + "challenges": [ + { + "id": "mkdir_1", + "title": "创建目录", + "description": "在 /tmp 下创建一个名为 'mydir' 的目录", + "hint": "mkdir /tmp/mydir", + "success_test": "exists('/tmp/mydir')", + "solution": ["mkdir /tmp/mydir"], + "success_msg": "📂 目录创建成功!" + }, + { + "id": "touch_1", + "title": "创建文件", + "description": "在 /tmp/mydir 下创建一个名为 'test.txt' 的空文件", + "hint": "touch /tmp/mydir/test.txt", + "success_test": "exists('/tmp/mydir/test.txt')", + "solution": ["touch /tmp/mydir/test.txt"], + "success_msg": "📄 文件创建成功!" + }, + { + "id": "cp_1", + "title": "复制文件", + "description": "将 /users/alice.txt 复制到 /tmp/mydir/", + "hint": "cp /users/alice.txt /tmp/mydir/", + "success_test": "exists('/tmp/mydir/alice.txt')", + "solution": ["cp /users/alice.txt /tmp/mydir/"], + "success_msg": "📋 复制成功!文件已备份" + }, + { + "id": "mv_1", + "title": "移动文件", + "description": "将 /tmp/mydir/alice.txt 重命名为 bob.txt", + "hint": "mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt", + "success_test": "exists('/tmp/mydir/bob.txt') and not exists('/tmp/mydir/alice.txt')", + "solution": ["mv /tmp/mydir/alice.txt /tmp/mydir/bob.txt"], + "success_msg": "🚚 移动成功!文件已改名" + }, + { + "id": "rm_1", + "title": "删除文件", + "description": "删除 /tmp/mydir/test.txt 文件", + "hint": "rm /tmp/mydir/test.txt", + "success_test": "not exists('/tmp/mydir/test.txt')", + "solution": ["rm /tmp/mydir/test.txt"], + "success_msg": "🗑️ 删除成功!文件已清理" + }, + { + "id": "chmod_1", + "title": "修改权限", + "description": "给 /tmp/mydir/bob.txt 添加可执行权限", + "hint": "chmod +x /tmp/mydir/bob.txt", + "success_test": "is_executable('/tmp/mydir/bob.txt')", + "solution": ["chmod +x /tmp/mydir/bob.txt"], + "success_msg": "🔐 权限修改成功!" + } + ] + }, + { + "id": "level_3_text", + "title": "🔍 Level 3: 文本处理 - 数据分析师", + "description": "掌握 grep、sed、awk 等强大的文本处理工具", + "challenges": [ + { + "id": "grep_1", + "title": "搜索文本", + "description": "在 /var/log/syslog 中搜索包含 'error' 的行", + "hint": "grep error /var/log/syslog", + "success_test": "'error' in output.lower()", + "solution": ["grep error /var/log/syslog", "grep -i error /var/log/syslog"], + "success_msg": "🎯 搜索成功!找到了错误日志" + }, + { + "id": "head_1", + "title": "查看开头", + "description": "查看 /var/log/syslog 的前 5 行", + "hint": "head -n 5 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 5", + "solution": ["head -5 /var/log/syslog", "head -n 5 /var/log/syslog"], + "success_msg": "👀 看到了开头!" + }, + { + "id": "tail_1", + "title": "查看结尾", + "description": "查看 /var/log/syslog 的最后 3 行", + "hint": "tail -n 3 /var/log/syslog", + "success_test": "len(output.split('\\n')) >= 3", + "solution": ["tail -3 /var/log/syslog", "tail -n 3 /var/log/syslog"], + "success_msg": "🔚 看到了结尾!" + }, + { + "id": "wc_1", + "title": "统计行数", + "description": "统计 /var/log/syslog 有多少行", + "hint": "wc -l /var/log/syslog", + "success_test": "output.strip().isdigit() or ' ' in output", + "solution": ["wc -l /var/log/syslog"], + "success_msg": "🔢 统计完成!" + }, + { + "id": "find_1", + "title": "查找文件", + "description": "在 /sandbox 下查找所有 .txt 文件", + "hint": "find /sandbox -name '*.txt'", + "success_test": "'.txt' in output", + "solution": ["find /sandbox -name '*.txt'"], + "success_msg": "🔍 查找成功!" + } + ] + }, + { + "id": "level_4_system", + "title": "⚙️ Level 4: 系统管理 - 运维工程师", + "description": "学习进程管理、磁盘监控、系统服务控制", + "challenges": [ + { + "id": "ps_1", + "title": "查看进程", + "description": "查看当前运行的所有进程", + "hint": "ps aux", + "success_test": "'PID' in output and 'COMMAND' in output", + "solution": ["ps aux", "ps -ef"], + "success_msg": "👥 进程列表已显示!" + }, + { + "id": "df_1", + "title": "磁盘空间", + "description": "查看磁盘使用情况", + "hint": "df -h", + "success_test": "'Filesystem' in output and 'Size' in output", + "solution": ["df -h"], + "success_msg": "💾 磁盘信息已获取!" + }, + { + "id": "du_1", + "title": "目录大小", + "description": "查看 /sandbox 目录的总大小", + "hint": "du -sh /sandbox", + "success_test": "'sandbox' in output or 'M' in output or 'K' in output", + "solution": ["du -sh /sandbox"], + "success_msg": "📊 大小统计完成!" + }, + { + "id": "free_1", + "title": "内存使用", + "description": "查看内存使用情况", + "hint": "free -h", + "success_test": "'Mem' in output and 'total' in output.lower()", + "solution": ["free -h"], + "success_msg": "🧠 内存信息已获取!" + }, + { + "id": "uptime_1", + "title": "系统运行时间", + "description": "查看系统已运行多久", + "hint": "uptime", + "success_test": "'load average' in output.lower() or 'day' in output or 'up' in output.lower()", + "solution": ["uptime"], + "success_msg": "⏱️ 运行时间已显示!" + } + ] + }, + { + "id": "level_5_network", + "title": "🌐 Level 5: 网络运维 - 网络管理员", + "description": "掌握网络诊断、连接监控、服务配置", + "challenges": [ + { + "id": "ping_1", + "title": "测试连通性", + "description": "ping 127.0.0.1 测试本地网络", + "hint": "ping -c 3 127.0.0.1", + "success_test": "'127.0.0.1' in output or 'bytes' in output or 'icmp' in output.lower()", + "solution": ["ping -c 3 127.0.0.1"], + "success_msg": "📡 网络连通!" + }, + { + "id": "netstat_1", + "title": "查看端口", + "description": "查看所有监听的端口", + "hint": "netstat -tlnp", + "success_test": "'LISTEN' in output or 'tcp' in output.lower()", + "solution": ["netstat -tlnp", "ss -tlnp"], + "success_msg": "🔌 端口列表已显示!" + }, + { + "id": "curl_1", + "title": "HTTP 请求", + "description": "使用 curl 访问 http://localhost:8080", + "hint": "curl http://localhost:8080", + "success_test": "'html' in output.lower() or 'http' in output.lower() or len(output) > 10", + "solution": ["curl http://localhost:8080", "curl -I http://localhost:8080"], + "success_msg": "🌐 HTTP 请求成功!" + }, + { + "id": "ifconfig_1", + "title": "查看网卡", + "description": "查看网络接口配置", + "hint": "ifconfig 或 ip addr", + "success_test": "'inet' in output or 'eth0' in output or 'lo' in output", + "solution": ["ifconfig", "ip addr"], + "success_msg": "🎴 网卡信息已获取!" + } + ] + }, + { + "id": "level_6_advanced", + "title": "🚀 Level 6: 高级运维 - 架构师", + "description": "Shell 脚本、日志分析、性能优化", + "challenges": [ + { + "id": "pipe_1", + "title": "管道操作", + "description": "使用管道 | 将 ps aux 的输出传给 grep 搜索 ssh", + "hint": "ps aux | grep ssh", + "success_test": "'|' in cmd and ('ssh' in output or 'grep' in output)", + "solution": ["ps aux | grep ssh"], + "success_msg": "🔀 管道操作成功!" + }, + { + "id": "redirect_1", + "title": "重定向输出", + "description": "将 echo 'test' 的输出重定向到 /tmp/test.txt", + "hint": "echo 'test' > /tmp/test.txt", + "success_test": "'>' in cmd and exists('/tmp/test.txt')", + "solution": ["echo 'test' > /tmp/test.txt"], + "success_msg": "📤 重定向成功!" + }, + { + "id": "tar_1", + "title": "压缩文件", + "description": "将 /sandbox 目录打包压缩为 /tmp/sandbox.tar.gz", + "hint": "tar -czf /tmp/sandbox.tar.gz /sandbox", + "success_test": "exists('/tmp/sandbox.tar.gz')", + "solution": ["tar -czf /tmp/sandbox.tar.gz /sandbox"], + "success_msg": "📦 压缩成功!" + }, + { + "id": "cron_1", + "title": "定时任务", + "description": "查看当前用户的定时任务", + "hint": "crontab -l", + "success_test": "'crontab' in cmd", + "solution": ["crontab -l"], + "success_msg": "⏰ 定时任务已查看!" + } + ] + } + ] +} diff --git a/__pycache__/sandbox.cpython-312.pyc b/__pycache__/sandbox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd59438c37f3814d84e71ee350bf61ea02ae3e2b GIT binary patch literal 21312 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

-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 literal 0 HcmV?d00001 diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b256205e049235041351571d0c75868a18dedf2c GIT binary patch literal 7881 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 aXCB!RC7Wl-LlN@ORkAIiL!@06r2htk*7ZmL literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..d98b19c --- /dev/null +++ b/index.html @@ -0,0 +1,926 @@ + + + + + + Linux 命令学习平台 - 从入门到精通 + + + +

+
+ +
+ + + +
+
+
+ +
+ + + + +
+ +
+

🚀 开启 Linux 学习之旅

+

+ 从入门到精通,系统学习 Linux 运维技能
+ 12 个级别 · 95 道题目 · 循序渐进 +

+ +
+ + +
+
+

任务标题

+
+ Level 1 + · + 1 / 95 +
+
+ +
+ +
+ + +
+
+ $ + +
+
输出将显示在这里...
+
+ +
+ + + +
+ + +
+
🎉 回答正确!
+

恭喜你完成了这个任务!

+ + +
+
+
+ + + +
+ + + + diff --git a/index.html.backup.1772810579 b/index.html.backup.1772810579 new file mode 100644 index 0000000..0c33c0f --- /dev/null +++ b/index.html.backup.1772810579 @@ -0,0 +1,582 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.backup.test b/index.html.backup.test new file mode 100644 index 0000000..0c33c0f --- /dev/null +++ b/index.html.backup.test @@ -0,0 +1,582 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.bak b/index.html.bak new file mode 100644 index 0000000..0c33c0f --- /dev/null +++ b/index.html.bak @@ -0,0 +1,582 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.bak.1772815479 b/index.html.bak.1772815479 new file mode 100644 index 0000000..d952b3f --- /dev/null +++ b/index.html.bak.1772815479 @@ -0,0 +1,644 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.debug b/index.html.debug new file mode 100644 index 0000000..6e9f51a --- /dev/null +++ b/index.html.debug @@ -0,0 +1,648 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.v2.bak b/index.html.v2.bak new file mode 100644 index 0000000..0c33c0f --- /dev/null +++ b/index.html.v2.bak @@ -0,0 +1,582 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index.html.v3.bak b/index.html.v3.bak new file mode 100644 index 0000000..0c33c0f --- /dev/null +++ b/index.html.v3.bak @@ -0,0 +1,582 @@ + + + + Linux 命令沙盒练习 - 菜鸟式学习平台 + + + + +
+

💻 Linux 命令沙盒练习

+
+ + 未登录 + +
+
+ +
+ + +
+ + +
+

欢迎来到 Linux 命令沙盒练习平台!

+

这是一个零风险的 Linux 命令学习环境,你可以安全地练习常用命令。

+

📌 使用说明:

+
    +
  • 左侧是课程目录,按顺序学习
  • +
  • 阅读任务描述,输入对应命令
  • +
  • 答对后自动解锁下一关
  • +
  • 支持深色/浅色主题切换
  • +
+ +
+
+
+ + + + + + diff --git a/index_new.html b/index_new.html new file mode 100644 index 0000000..d98b19c --- /dev/null +++ b/index_new.html @@ -0,0 +1,926 @@ + + + + + + Linux 命令学习平台 - 从入门到精通 + + + +
+
+ +
+ + + +
+
+
+ +
+ + + + +
+ +
+

🚀 开启 Linux 学习之旅

+

+ 从入门到精通,系统学习 Linux 运维技能
+ 12 个级别 · 95 道题目 · 循序渐进 +

+ +
+ + +
+
+

任务标题

+
+ Level 1 + · + 1 / 95 +
+
+ +
+ +
+ + +
+
+ $ + +
+
输出将显示在这里...
+
+ +
+ + + +
+ + +
+
🎉 回答正确!
+

恭喜你完成了这个任务!

+ + +
+
+
+ + + +
+ + + + diff --git a/linux-practice.service b/linux-practice.service new file mode 100644 index 0000000..0c90b50 --- /dev/null +++ b/linux-practice.service @@ -0,0 +1,17 @@ +[Unit] +Description=Linux Practice Server (reverse proxied by Caddy) +After=network.target + +[Service] +Type=simple +User=llm +Group=llm +WorkingDirectory=/home/llm/projects/linux-practice +ExecStart=/usr/bin/python3 /home/llm/projects/linux-practice/server.py +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/sandbox.py b/sandbox.py new file mode 100644 index 0000000..9149f8c --- /dev/null +++ b/sandbox.py @@ -0,0 +1,581 @@ +#!/usr/bin/env python3 +""" +Linux 命令沙盒模拟器 +- 零风险:不真实执行系统命令 +- 模拟文件系统:虚拟内存字典结构 +- 命令白名单:只允许安全命令 +- 场景匹配:检查命令是否答对 +""" + +import re + +# ============================== +# 1. 虚拟文件系统(内存树结构) +# ============================== +SANDBOX_FS = { + "/": { + "type": "dir", + "perm": "r-x", + "children": ["users", "projects", "logs", "sandbox_tips"], + }, + "/users": { + "type": "dir", + "perm": "r-x", + "children": ["alice.txt", "bob.txt", "charlie"], + }, + "/users/alice.txt": { + "type": "file", + "perm": "r--", + "content": "Hi, I'm Alice. I love Linux!", + }, + "/users/bob.txt": { + "type": "file", + "perm": "r--", + "content": "Hello from Bob. Learning Linux daily.", + }, + "/users/charlie": { + "type": "dir", + "perm": "r-x", + "children": ["profile.md", "skills.txt"], + }, + "/users/charlie/profile.md": { + "type": "file", + "perm": "r--", + "content": "# Charlie\nLoves open source and Linux commands.", + }, + "/users/charlie/skills.txt": { + "type": "file", + "perm": "r--", + "content": "Linux\nPython\nGit\nDocker", + }, + "/projects": { + "type": "dir", + "perm": "r-x", + "children": ["web", "backend"], + }, + "/projects/web": { + "type": "dir", + "perm": "r-x", + "children": ["index.html", "style.css"], + }, + "/projects/web/index.html": { + "type": "file", + "perm": "r--", + "content": "Hello Linux Sandbox", + }, + "/projects/web/style.css": { + "type": "file", + "perm": "r--", + "content": "body { font-family: sans-serif; }", + }, + "/projects/backend": { + "type": "dir", + "perm": "r-x", + "children": ["app.py", "model.py", "utils.py"], + }, + "/projects/backend/app.py": { + "type": "file", + "perm": "rw-", + "content": "def main():\n print('Hello, Linux!')\n\nif __name__ == '__main__':\n main()", + }, + "/projects/backend/model.py": { + "type": "file", + "perm": "rw-", + "content": "class User:\n def __init__(self, name):\n self.name = name", + }, + "/projects/backend/utils.py": { + "type": "file", + "perm": "rw-", + "content": "def log(msg):\n print(f'[LOG] {msg}')", + }, + "/logs": { + "type": "dir", + "perm": "r-x", + "children": ["access.log", "error.log"], + }, + "/logs/access.log": { + "type": "file", + "perm": "r--", + "content": "2024-01-01 10:00 GET /index.html 200\n2024-01-01 10:01 POST /api/login 200\n2024-01-01 10:02 GET /about 404", + }, + "/logs/error.log": { + "type": "file", + "perm": "r--", + "content": "2024-01-01 10:02 ERROR Page not found\n2024-01-01 10:03 WARNING High memory usage", + }, + "/sandbox_tips": { + "type": "dir", + "perm": "r-x", + "children": ["welcome.txt"], + }, + "/sandbox_tips/welcome.txt": { + "type": "file", + "perm": "r--", + "content": "Welcome to Linux Sandbox!\n\nYou can practice commands safely here.\nTry 'ls', 'cat', 'grep', 'find' and more!", + }, +} + +# ============================== +# 2. 白名单命令定义 +# ============================== +# 命令 → 参数验证规则 +ALLOWED_CMDS = { + "ls": { + "flags": ["-l", "-a", "-h", "-R", "-t", "-S"], + "path": True, + }, + "cd": {"path": True}, + "pwd": {}, + "echo": {"rest": True}, + "cat": {"path": True}, + "head": {"flags": ["-n"], "path": True}, + "tail": {"flags": ["-n"], "path": True}, + "grep": {"flags": ["-i", "-r", "-n"], "pattern": True, "path": True}, + "find": { + "flags": ["-type", "-name", "-size"], + "path": True, + }, + "du": {"flags": ["-h", "-s"], "path": True}, + "sort": {"flags": ["-r", "-n", "-h"]}, + "wc": {"flags": ["-l", "-w", "-c"], "path": True}, + "mkdir": {"path": True}, + "touch": {"path": True}, + "cp": {"path": True}, # 模拟副本 + "mv": {"path": True}, # 模拟重命名 + "whoami": {}, + "history": {"flags": ["-n"]}, + "stat": {"path": True}, +} + +# ============================== +# 3. 沙盒核心类 +# ============================== +class LinuxSandbox: + def __init__(self): + self.cwd = "/" # 当前目录 + self.history = [] # 命令历史 + self.current_task = None + + def check_permissions(self, cmd_name: str, args: list) -> tuple[bool, str]: + """验证命令是否在白名单 + 参数是否合法""" + if cmd_name not in ALLOWED_CMDS: + return False, f"❌ 拒绝执行 '{cmd_name}'(不在允许列表)" + + rule = ALLOWED_CMDS[cmd_name] + + # 检查非法参数 + for arg in args: + if arg.startswith("-"): + if "flags" in rule and arg not in rule["flags"]: + return False, f"❌ 不支持参数: {arg}" + + return True, "" + + def resolve_path(self, path: str) -> str: + """解析相对/绝对路径(简化版)""" + if path.startswith("/"): + return path + if path == ".": + return self.cwd + if path == "..": + parent = "/".join(self.cwd.split("/")[:-1]) or "/" + return parent if parent else "/" + return f"{self.cwd.rstrip('/')}/{path}" + + def get_fspath(self, path: str) -> dict | None: + """从虚拟 FS 中获取节点""" + return SANDBOX_FS.get(path) + + def _simulate_ls(self, args: list) -> str: + """模拟 ls 命令""" + if not args: + # ls 默认列出当前目录 + node = SANDBOX_FS.get(self.cwd) + if not node or node.get("type") != "dir": + return f"ls: {self.cwd}: Not a directory" + + children = node.get("children", []) + return " ".join(children) + + # 处理路径参数 + path = args[0] + resolved = self.resolve_path(path) + node = self.get_fspath(resolved) + + if not node: + return f"ls: cannot access '{path}': No such file or directory" + if node["type"] != "dir": + return f"ls: cannot access '{path}': Not a directory" + + return " ".join(node.get("children", [])) + + def _simulate_cat(self, args: list) -> str: + """模拟 cat 命令""" + if not args: + return "cat: missing operand" + + path = args[0] + resolved = self.resolve_path(path) + node = self.get_fspath(resolved) + + if not node: + return f"cat: {path}: No such file or directory" + if node["type"] != "file": + return f"cat: {path}: Is a directory" + + return node.get("content", "") + + def _simulate_cd(self, args: list) -> str: + """模拟 cd 命令""" + if not args or args[0] == "~": + self.cwd = "/home" + return "" + + path = args[0] + resolved = self.resolve_path(path) + node = self.get_fspath(resolved) + + if not node or node["type"] != "dir": + return f"cd: {path}: No such file or directory" + + self.cwd = resolved + return "" + + def _simulate_pwd(self) -> str: + """模拟 pwd 命令""" + return self.cwd + + def _simulate_echo(self, args: list) -> str: + """模拟 echo 命令""" + return " ".join(args) + + def _simulate_grep(self, args: list) -> str: + """模拟 grep 命令""" + # grep pattern file or grep -r pattern dir + if len(args) < 1: + return "grep: missing file operand" + + pattern = "" + paths = [] + + # 简化解析:第一个非 flag 参数是 pattern + i = 0 + while i < len(args): + if args[i] == "-r" or args[i] == "-i" or args[i].startswith("-"): + i += 1 + else: + if not pattern: + pattern = args[i] + else: + paths.append(args[i]) + i += 1 + + if not pattern or not paths: + return "grep: pattern or file missing" + + # 支持通配符匹配 + import re + results = [] + + for path_arg in paths: + # 处理 * 通配符 + if "*" in path_arg: + # 简单 glob 模拟 + prefix = path_arg.split("/")[0] if "/" in path_arg else self.cwd.rstrip("/") + pattern_part = path_arg.replace("*", ".*") + for key in SANDBOX_FS: + if key.startswith(prefix) and SANDBOX_FS[key]["type"] == "file": + if re.match(pattern_part, key.split("/")[-1]): + node = self.get_fspath(key) + if node and node["type"] == "file": + lines = node.get("content", "").split("\n") + for line in lines: + if pattern in line: + results.append(f"{key}:{line}") + else: + resolved = self.resolve_path(path_arg) + node = self.get_fspath(resolved) + if not node: + return f"grep: {path_arg}: No such file or directory" + if node["type"] != "file": + return f"grep: {path_arg}: Is a directory" + lines = node.get("content", "").split("\n") + for line in lines: + if pattern in line: + results.append(line) + + return "\n".join(results) if results else "" + + def _simulate_find(self, args: list) -> str: + """模拟 find 命令(简化版)""" + # find path -type f -name "*.ext" + # 兼容不同顺序:find /path -type f -name "*.py" or find /path -name "*.py" + + path = self.cwd + file_type = None + name_pattern = None + + i = 0 + while i < len(args): + arg = args[i] + if arg.startswith("-"): + if arg == "-type" and i + 1 < len(args): + file_type = args[i + 1] + i += 2 + elif arg == "-name" and i + 1 < len(args): + name_pattern = args[i + 1] + i += 2 + else: + i += 1 + else: + path = self.resolve_path(arg) + i += 1 + + # 如果没有指定文件类型,默认查找所有文件 + if file_type is None: + file_type = "f" + + # 如果没有指定 -name,返回所有文件 + if name_pattern is None: + name_pattern = "*" + + # 匹配逻辑 + results = [] + for key in SANDBOX_FS: + if key.startswith(path) and SANDBOX_FS[key]["type"] == file_type: + # 简单 glob 匹配 + filename = key.split("/")[-1] + if name_pattern == "*" or filename.endswith(name_pattern.replace("*", "")): + results.append(key) + + return "\n".join(results) if results else "" + + def _simulate_du(self, args: list) -> str: + """模拟 du 命令""" + # du -sh * or du -h path + path = self.cwd + show_all = False + + # 解析参数 + i = 0 + while i < len(args): + if args[i] == "-sh" or args[i] == "-h": + show_all = True + i += 1 + elif args[i].startswith("-"): + i += 1 + else: + path = self.resolve_path(args[i]) + i += 1 + + node = self.get_fspath(path) + if not node or node["type"] != "dir": + return f"du: {path}: No such file or directory" + + # 模拟大小(KB)- 简化版 + size_map = {"file": 1, "dir": 4} + total = 0 + children = node.get("children", []) + for child in children: + child_path = f"{path.rstrip('/')}/{child}" + child_node = self.get_fspath(child_path) + if child_node: + total += size_map.get(child_node["type"], 1) + + return f"{total}K\t{path}" + + def _simulate_sort(self, args: list, input_text: str) -> str: + """模拟 sort 命令""" + lines = input_text.strip().split("\n") + reverse = "-r" in args + numeric = "-n" in args + + if numeric: + try: + lines = sorted(lines, key=lambda x: float(x.split()[0]), reverse=reverse) + except: + pass + else: + lines.sort(reverse=reverse) + + return "\n".join(lines) + + def _simulate_wc(self, args: list, input_text: str = "") -> str: + """模拟 wc 命令""" + lines = input_text.count("\n") + 1 if input_text else 0 + words = len(input_text.split()) if input_text else 0 + chars = len(input_text) + + # 从文件计算 + if args and args[-1] not in ["-l", "-w", "-c"]: + path = args[-1] + resolved = self.resolve_path(path) + node = self.get_fspath(resolved) + if node and node["type"] == "file": + content = node.get("content", "") + lines = content.count("\n") + 1 + words = len(content.split()) + chars = len(content) + + flag = args[0] if args and args[0].startswith("-") else "" + if flag == "-l": + return str(lines) + elif flag == "-w": + return str(words) + elif flag == "-c": + return str(chars) + else: + return f" {lines} {words} {chars}" + + def _simulate_mkdir(self, args: list) -> str: + """模拟 mkdir -p""" + if not args: + return "mkdir: missing operand" + + path = self.resolve_path(args[0]) + # 简化:只允许在 sandbox 下创建 + if not path.startswith("/sandbox"): + return "mkdir: permission denied: only sandbox paths allowed" + + return f"mkdir: created directory '{path}' (simulated)" + + def _simulate_touch(self, args: list) -> str: + """模拟 touch""" + if not args: + return "touch: missing file operand" + + path = self.resolve_path(args[0]) + # 只读沙盒,不允许创建 + return f"touch: cannot touch '{path}': Read-only file system" + + def _simulate_cp(self, args: list) -> str: + """模拟 cp""" + if len(args) < 2: + return "cp: missing file operand" + + src = self.resolve_path(args[0]) + dst = args[-1] # 简化:目标总是最后一个参数 + + if not self.get_fspath(src): + return f"cp: cannot stat '{args[0]}': No such file or directory" + + return f"cp: copied (simulated) '{src}' → '{dst}'" + + def _simulate_mv(self, args: list) -> str: + """模拟 mv""" + if len(args) < 2: + return "mv: missing file operand" + + return f"mv: moved (simulated) '{args[0]}' → '{args[1]}'" + + def _simulate_stat(self, args: list) -> str: + """模拟 stat""" + if not args: + return "stat: missing operand" + + path = self.resolve_path(args[0]) + node = self.get_fspath(path) + + if not node: + return f"stat: cannot stat '{path}': No such file or directory" + + return f""" File: {path} + Size: {len(node.get('content', ''))} Blocks: 1 IO Block: 4096 {'directory' if node['type'] == 'dir' else 'regular file'} +Access: ({node['perm']}/0{'' if node['type']=='dir' else '0' }44) uid=1000 gid=1000 +Access: 2026-03-04 00:00:00.000000000 +Modify: 2026-03-04 00:00:00.000000000 +Change: 2026-03-04 00:00:00.000000000 + Birth: -""" + + # ============================== + # 主执行入口 + # ============================== + def execute(self, cmd: str) -> dict: + """执行命令,返回结果 & 社交化反馈""" + cmd = cmd.strip() + if not cmd: + return {"output": "", "success": True, "message": ""} + + # 记录历史 + self.history.append(cmd) + + # 解析命令 + parts = cmd.split() + cmd_name = parts[0] + args = parts[1:] + + # 安全检查 + # 1. 关键危险命令拦截 + if any(x in cmd for x in ["rm -rf", "sudo", "chmod 777", "wget", "curl"]): + return { + "output": "", + "success": False, + "message": "❌ 拒绝执行危险命令!这是沙盒环境,为安全起见禁止危险指令。", + } + + # 2. 白名单验证 + allowed, err = self.check_permissions(cmd_name, args) + if not allowed: + return {"output": "", "success": False, "message": err} + + # 执行模拟命令 + output = "" + try: + if cmd_name == "ls": + output = self._simulate_ls(args) + elif cmd_name == "cd": + msg = self._simulate_cd(args) + output = msg if msg else self.cwd + elif cmd_name == "pwd": + output = self._simulate_pwd() + elif cmd_name == "echo": + output = self._simulate_echo(args) + elif cmd_name == "cat": + output = self._simulate_cat(args) + elif cmd_name == "grep": + output = self._simulate_grep(args) + elif cmd_name == "find": + output = self._simulate_find(args) + elif cmd_name == "du": + output = self._simulate_du(args) + elif cmd_name == "sort": + output = self._simulate_sort(args, "") + elif cmd_name == "wc": + output = self._simulate_wc(args) + elif cmd_name == "mkdir": + output = self._simulate_mkdir(args) + elif cmd_name == "touch": + output = self._simulate_touch(args) + elif cmd_name == "cp": + output = self._simulate_cp(args) + elif cmd_name == "mv": + output = self._simulate_mv(args) + elif cmd_name == "whoami": + output = "sandbox_user" + elif cmd_name == "history": + n = int(args[0].lstrip("-n")) if args and args[0].startswith("-n") else 5 + output = "\n".join(f" {i+1} {h}" for i, h in enumerate(self.history[-n:])) + elif cmd_name == "stat": + output = self._simulate_stat(args) + else: + output = f"{cmd_name}: command not found (sandbox mode)" + + except Exception as e: + output = f"Error: {e}" + + return {"output": output, "success": True, "message": "✅ 命令执行成功"} + + +# ============================== +# 4. 单元测试(可选) +# ============================== +if __name__ == "__main__": + sb = LinuxSandbox() + print("=== Linux Sandbox Test ===") + print(sb.execute("ls")) + print(sb.execute("pwd")) + print(sb.execute("cat /users/alice.txt")) + print(sb.execute("grep Linux /users/*")) + print(sb.execute("find /projects -name '*.py'")) + print(sb.execute("du -sh /projects")) + print(sb.execute("rm -rf /")) # 应该被拦截 diff --git a/server.py b/server.py new file mode 100755 index 0000000..6ed635a --- /dev/null +++ b/server.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +Linux 命令沙盒练习平台 Server +- 集成 sandbox.py 沙盒引擎 +- 路由:/ → HTML 页面, /api/run → 命令执行, /api/check → 任务检查 +""" + +import http.server +import urllib.parse +import json +import os +import base64 +import hashlib + +# ============================== +# 认证配置 +# ============================== +# 预置用户:{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 + +SANDBOX = LinuxSandbox() + +# ============================== +# 任务数据加载 +# ============================== +TASKS_FILE = os.path.join(os.path.dirname(__file__), "COURSE_TASKS.json") + + +def load_tasks(): + """加载课程任务""" + 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": []} + + +TASKS = load_tasks() + +# ============================== +# Handler 类 +# ============================== +class LinuxSandboxHandler(http.server.BaseHTTPRequestHandler): + def send_json(self, data, status=200): + """发送 JSON 响应""" + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) + + def send_text(self, text, status=200): + """发送纯文本响应""" + self.send_response(status) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + self.wfile.write(text.encode("utf-8")) + + 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 + 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 + + # 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()) + + # 2. 命令执行 API + elif 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) + + # 3. 任务检查 API + elif 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) + 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) + 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 = "✅ 回答正确!🎉" + + self.send_json({ + "task_id": task_id, + "success": success, + "message": message, + "hint": task.get("hint", "💡 没有提示"), + "title": task.get("title", "未知任务"), + }) + + # 4. 获取课程总览 + elif path == "/api/tasks": + self.send_json(TASKS) + + # 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 + + # /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) + + # /api/logout + elif path == "/api/logout": + self.send_json({"success": True, "message": "👋 已退出登录"}) + + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") + + +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() diff --git a/server.py.bak b/server.py.bak new file mode 100755 index 0000000..6ed635a --- /dev/null +++ b/server.py.bak @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +Linux 命令沙盒练习平台 Server +- 集成 sandbox.py 沙盒引擎 +- 路由:/ → HTML 页面, /api/run → 命令执行, /api/check → 任务检查 +""" + +import http.server +import urllib.parse +import json +import os +import base64 +import hashlib + +# ============================== +# 认证配置 +# ============================== +# 预置用户:{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 + +SANDBOX = LinuxSandbox() + +# ============================== +# 任务数据加载 +# ============================== +TASKS_FILE = os.path.join(os.path.dirname(__file__), "COURSE_TASKS.json") + + +def load_tasks(): + """加载课程任务""" + 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": []} + + +TASKS = load_tasks() + +# ============================== +# Handler 类 +# ============================== +class LinuxSandboxHandler(http.server.BaseHTTPRequestHandler): + def send_json(self, data, status=200): + """发送 JSON 响应""" + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) + + def send_text(self, text, status=200): + """发送纯文本响应""" + self.send_response(status) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + self.wfile.write(text.encode("utf-8")) + + 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 + 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 + + # 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()) + + # 2. 命令执行 API + elif 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) + + # 3. 任务检查 API + elif 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) + 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) + 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 = "✅ 回答正确!🎉" + + self.send_json({ + "task_id": task_id, + "success": success, + "message": message, + "hint": task.get("hint", "💡 没有提示"), + "title": task.get("title", "未知任务"), + }) + + # 4. 获取课程总览 + elif path == "/api/tasks": + self.send_json(TASKS) + + # 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 + + # /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) + + # /api/logout + elif path == "/api/logout": + self.send_json({"success": True, "message": "👋 已退出登录"}) + + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not Found") + + +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()