commit 5686831d9ace02b7657a5608060c6be665ac3417 Author: likingcode Date: Sat Mar 7 05:43:51 2026 +0000 feat: Linux练习平台 - Web界面Linux命令练习 - Python后端 + sandbox安全沙箱 - 课程和任务管理 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 0000000..fd59438 Binary files /dev/null and b/__pycache__/sandbox.cpython-312.pyc differ diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc new file mode 100644 index 0000000..b256205 Binary files /dev/null and b/__pycache__/server.cpython-312.pyc differ 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()