manage.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 新闻爬虫容器管理工具 - supercronic
  5. """
  6. import os
  7. import sys
  8. import subprocess
  9. import time
  10. import signal
  11. from pathlib import Path
  12. # Web 服务器配置
  13. WEBSERVER_PORT = int(os.environ.get("WEBSERVER_PORT", "8080"))
  14. WEBSERVER_DIR = "/app/output"
  15. WEBSERVER_PID_FILE = "/tmp/webserver.pid"
  16. def run_command(cmd, shell=True, capture_output=True):
  17. """执行系统命令"""
  18. try:
  19. result = subprocess.run(
  20. cmd, shell=shell, capture_output=capture_output, text=True
  21. )
  22. return result.returncode == 0, result.stdout, result.stderr
  23. except Exception as e:
  24. return False, "", str(e)
  25. def manual_run():
  26. """手动执行一次爬虫"""
  27. print("🔄 手动执行爬虫...")
  28. try:
  29. result = subprocess.run(
  30. ["python", "-m", "trendradar"], cwd="/app", capture_output=False, text=True
  31. )
  32. if result.returncode == 0:
  33. print("✅ 执行完成")
  34. else:
  35. print(f"❌ 执行失败,退出码: {result.returncode}")
  36. except Exception as e:
  37. print(f"❌ 执行出错: {e}")
  38. def parse_cron_schedule(cron_expr):
  39. """解析cron表达式并返回人类可读的描述"""
  40. if not cron_expr or cron_expr == "未设置":
  41. return "未设置"
  42. try:
  43. parts = cron_expr.strip().split()
  44. if len(parts) != 5:
  45. return f"原始表达式: {cron_expr}"
  46. minute, hour, day, month, weekday = parts
  47. # 分析分钟
  48. if minute == "*":
  49. minute_desc = "每分钟"
  50. elif minute.startswith("*/"):
  51. interval = minute[2:]
  52. minute_desc = f"每{interval}分钟"
  53. elif "," in minute:
  54. minute_desc = f"在第{minute}分钟"
  55. else:
  56. minute_desc = f"在第{minute}分钟"
  57. # 分析小时
  58. if hour == "*":
  59. hour_desc = "每小时"
  60. elif hour.startswith("*/"):
  61. interval = hour[2:]
  62. hour_desc = f"每{interval}小时"
  63. elif "," in hour:
  64. hour_desc = f"在{hour}点"
  65. else:
  66. hour_desc = f"在{hour}点"
  67. # 分析日期
  68. if day == "*":
  69. day_desc = "每天"
  70. elif day.startswith("*/"):
  71. interval = day[2:]
  72. day_desc = f"每{interval}天"
  73. else:
  74. day_desc = f"每月{day}号"
  75. # 分析月份
  76. if month == "*":
  77. month_desc = "每月"
  78. else:
  79. month_desc = f"在{month}月"
  80. # 分析星期
  81. weekday_names = {
  82. "0": "周日", "1": "周一", "2": "周二", "3": "周三",
  83. "4": "周四", "5": "周五", "6": "周六", "7": "周日"
  84. }
  85. if weekday == "*":
  86. weekday_desc = ""
  87. else:
  88. weekday_desc = f"在{weekday_names.get(weekday, weekday)}"
  89. # 组合描述
  90. if minute.startswith("*/") and hour == "*" and day == "*" and month == "*" and weekday == "*":
  91. # 简单的间隔模式,如 */30 * * * *
  92. return f"每{minute[2:]}分钟执行一次"
  93. elif hour != "*" and minute != "*" and day == "*" and month == "*" and weekday == "*":
  94. # 每天特定时间,如 0 9 * * *
  95. return f"每天{hour}:{minute.zfill(2)}执行"
  96. elif weekday != "*" and day == "*":
  97. # 每周特定时间
  98. return f"{weekday_desc}{hour}:{minute.zfill(2)}执行"
  99. else:
  100. # 复杂模式,显示详细信息
  101. desc_parts = [part for part in [month_desc, day_desc, weekday_desc, hour_desc, minute_desc] if part and part != "每月" and part != "每天" and part != "每小时"]
  102. if desc_parts:
  103. return " ".join(desc_parts) + "执行"
  104. else:
  105. return f"复杂表达式: {cron_expr}"
  106. except Exception as e:
  107. return f"解析失败: {cron_expr}"
  108. def show_status():
  109. """显示容器状态"""
  110. print("📊 容器状态:")
  111. # 检查 PID 1 状态
  112. supercronic_is_pid1 = False
  113. pid1_cmdline = ""
  114. try:
  115. with open('/proc/1/cmdline', 'r') as f:
  116. pid1_cmdline = f.read().replace('\x00', ' ').strip()
  117. print(f" 🔍 PID 1 进程: {pid1_cmdline}")
  118. if "supercronic" in pid1_cmdline.lower():
  119. print(" ✅ supercronic 正确运行为 PID 1")
  120. supercronic_is_pid1 = True
  121. else:
  122. print(" ❌ PID 1 不是 supercronic")
  123. print(f" 📋 实际的 PID 1: {pid1_cmdline}")
  124. except Exception as e:
  125. print(f" ❌ 无法读取 PID 1 信息: {e}")
  126. # 检查环境变量
  127. cron_schedule = os.environ.get("CRON_SCHEDULE", "未设置")
  128. run_mode = os.environ.get("RUN_MODE", "未设置")
  129. immediate_run = os.environ.get("IMMEDIATE_RUN", "未设置")
  130. print(f" ⚙️ 运行配置:")
  131. print(f" CRON_SCHEDULE: {cron_schedule}")
  132. # 解析并显示cron表达式的含义
  133. cron_description = parse_cron_schedule(cron_schedule)
  134. print(f" ⏰ 执行频率: {cron_description}")
  135. print(f" RUN_MODE: {run_mode}")
  136. print(f" IMMEDIATE_RUN: {immediate_run}")
  137. # 检查配置文件
  138. config_files = ["/app/config/config.yaml", "/app/config/frequency_words.txt"]
  139. print(" 📁 配置文件:")
  140. for file_path in config_files:
  141. if Path(file_path).exists():
  142. print(f" ✅ {Path(file_path).name}")
  143. else:
  144. print(f" ❌ {Path(file_path).name} 缺失")
  145. # 检查关键文件
  146. key_files = [
  147. ("/usr/local/bin/supercronic-linux-amd64", "supercronic二进制文件"),
  148. ("/usr/local/bin/supercronic", "supercronic软链接"),
  149. ("/tmp/crontab", "crontab文件"),
  150. ("/entrypoint.sh", "启动脚本")
  151. ]
  152. print(" 📂 关键文件检查:")
  153. for file_path, description in key_files:
  154. if Path(file_path).exists():
  155. print(f" ✅ {description}: 存在")
  156. # 对于crontab文件,显示内容
  157. if file_path == "/tmp/crontab":
  158. try:
  159. with open(file_path, 'r') as f:
  160. crontab_content = f.read().strip()
  161. print(f" 内容: {crontab_content}")
  162. except:
  163. pass
  164. else:
  165. print(f" ❌ {description}: 不存在")
  166. # 检查容器运行时间
  167. print(" ⏱️ 容器时间信息:")
  168. try:
  169. # 检查 PID 1 的启动时间
  170. with open('/proc/1/stat', 'r') as f:
  171. stat_content = f.read().strip().split()
  172. if len(stat_content) >= 22:
  173. # starttime 是第22个字段(索引21)
  174. starttime_ticks = int(stat_content[21])
  175. # 读取系统启动时间
  176. with open('/proc/stat', 'r') as stat_f:
  177. for line in stat_f:
  178. if line.startswith('btime'):
  179. boot_time = int(line.split()[1])
  180. break
  181. else:
  182. boot_time = 0
  183. # 读取系统时钟频率
  184. clock_ticks = os.sysconf(os.sysconf_names['SC_CLK_TCK'])
  185. if boot_time > 0:
  186. pid1_start_time = boot_time + (starttime_ticks / clock_ticks)
  187. current_time = time.time()
  188. uptime_seconds = int(current_time - pid1_start_time)
  189. uptime_minutes = uptime_seconds // 60
  190. uptime_hours = uptime_minutes // 60
  191. if uptime_hours > 0:
  192. print(f" PID 1 运行时间: {uptime_hours} 小时 {uptime_minutes % 60} 分钟")
  193. else:
  194. print(f" PID 1 运行时间: {uptime_minutes} 分钟 ({uptime_seconds} 秒)")
  195. else:
  196. print(f" PID 1 运行时间: 无法精确计算")
  197. else:
  198. print(" ❌ 无法解析 PID 1 统计信息")
  199. except Exception as e:
  200. print(f" ❌ 时间检查失败: {e}")
  201. # 状态总结和建议
  202. print(" 📊 状态总结:")
  203. if supercronic_is_pid1:
  204. print(" ✅ supercronic 正确运行为 PID 1")
  205. print(" ✅ 定时任务应该正常工作")
  206. # 显示当前的调度信息
  207. if cron_schedule != "未设置":
  208. print(f" ⏰ 当前调度: {cron_description}")
  209. # 提供一些常见的调度建议
  210. if "分钟" in cron_description and "每30分钟" not in cron_description and "每60分钟" not in cron_description:
  211. print(" 💡 频繁执行模式,适合实时监控")
  212. elif "小时" in cron_description:
  213. print(" 💡 按小时执行模式,适合定期汇总")
  214. elif "天" in cron_description:
  215. print(" 💡 每日执行模式,适合日报生成")
  216. print(" 💡 如果定时任务不执行,检查:")
  217. print(" • crontab 格式是否正确")
  218. print(" • 时区设置是否正确")
  219. print(" • 应用程序是否有错误")
  220. else:
  221. print(" ❌ supercronic 状态异常")
  222. if pid1_cmdline:
  223. print(f" 📋 当前 PID 1: {pid1_cmdline}")
  224. print(" 💡 建议操作:")
  225. print(" • 重启容器: docker restart trend-radar")
  226. print(" • 检查容器日志: docker logs trend-radar")
  227. # 显示日志检查建议
  228. print(" 📋 运行状态检查:")
  229. print(" • 查看完整容器日志: docker logs trend-radar")
  230. print(" • 查看实时日志: docker logs -f trend-radar")
  231. print(" • 手动执行测试: python manage.py run")
  232. print(" • 重启容器服务: docker restart trend-radar")
  233. def show_config():
  234. """显示当前配置"""
  235. print("⚙️ 当前配置:")
  236. env_vars = [
  237. "CRON_SCHEDULE",
  238. "RUN_MODE",
  239. "IMMEDIATE_RUN",
  240. "FEISHU_WEBHOOK_URL",
  241. "DINGTALK_WEBHOOK_URL",
  242. "WEWORK_WEBHOOK_URL",
  243. "TELEGRAM_BOT_TOKEN",
  244. "TELEGRAM_CHAT_ID",
  245. "CONFIG_PATH",
  246. "FREQUENCY_WORDS_PATH",
  247. # 存储配置
  248. "STORAGE_BACKEND",
  249. "LOCAL_RETENTION_DAYS",
  250. "REMOTE_RETENTION_DAYS",
  251. "STORAGE_TXT_ENABLED",
  252. "STORAGE_HTML_ENABLED",
  253. "S3_BUCKET_NAME",
  254. "S3_ACCESS_KEY_ID",
  255. "S3_ENDPOINT_URL",
  256. "S3_REGION",
  257. "PULL_ENABLED",
  258. "PULL_DAYS",
  259. ]
  260. for var in env_vars:
  261. value = os.environ.get(var, "未设置")
  262. # 隐藏敏感信息
  263. if any(sensitive in var for sensitive in ["WEBHOOK", "TOKEN", "KEY", "SECRET"]):
  264. if value and value != "未设置":
  265. masked_value = value[:10] + "***" if len(value) > 10 else "***"
  266. print(f" {var}: {masked_value}")
  267. else:
  268. print(f" {var}: {value}")
  269. else:
  270. print(f" {var}: {value}")
  271. crontab_file = "/tmp/crontab"
  272. if Path(crontab_file).exists():
  273. print(" 📅 Crontab内容:")
  274. try:
  275. with open(crontab_file, "r") as f:
  276. content = f.read().strip()
  277. print(f" {content}")
  278. except Exception as e:
  279. print(f" 读取失败: {e}")
  280. else:
  281. print(" 📅 Crontab文件不存在")
  282. def show_files():
  283. """显示输出文件"""
  284. print("📁 输出文件:")
  285. output_dir = Path("/app/output")
  286. if not output_dir.exists():
  287. print(" 📭 输出目录不存在")
  288. return
  289. # 显示最近的文件
  290. date_dirs = sorted([d for d in output_dir.iterdir() if d.is_dir()], reverse=True)
  291. if not date_dirs:
  292. print(" 📭 输出目录为空")
  293. return
  294. # 显示最近2天的文件
  295. for date_dir in date_dirs[:2]:
  296. print(f" 📅 {date_dir.name}:")
  297. # 检查 SQLite 数据库文件
  298. db_files = list(date_dir.glob("*.db"))
  299. if db_files:
  300. print(f" 💾 SQLite: {len(db_files)} 个数据库")
  301. for db_file in db_files[:3]:
  302. mtime = time.ctime(db_file.stat().st_mtime)
  303. size_kb = db_file.stat().st_size // 1024
  304. print(f" 📀 {db_file.name} ({size_kb}KB, {mtime.split()[3][:5]})")
  305. # 检查子目录(html, txt)
  306. for subdir in ["html", "txt"]:
  307. sub_path = date_dir / subdir
  308. if sub_path.exists():
  309. files = list(sub_path.glob("*"))
  310. if files:
  311. recent_files = sorted(
  312. files, key=lambda x: x.stat().st_mtime, reverse=True
  313. )[:3]
  314. print(f" 📂 {subdir}: {len(files)} 个文件")
  315. for file in recent_files:
  316. mtime = time.ctime(file.stat().st_mtime)
  317. size_kb = file.stat().st_size // 1024
  318. print(
  319. f" 📄 {file.name} ({size_kb}KB, {mtime.split()[3][:5]})"
  320. )
  321. else:
  322. print(f" 📂 {subdir}: 空")
  323. def show_logs():
  324. """显示实时日志"""
  325. print("📋 实时日志 (按 Ctrl+C 退出):")
  326. print("💡 提示: 这将显示 PID 1 进程的输出")
  327. try:
  328. # 尝试多种方法查看日志
  329. log_files = [
  330. "/proc/1/fd/1", # PID 1 的标准输出
  331. "/proc/1/fd/2", # PID 1 的标准错误
  332. ]
  333. for log_file in log_files:
  334. if Path(log_file).exists():
  335. print(f"📄 尝试读取: {log_file}")
  336. subprocess.run(["tail", "-f", log_file], check=True)
  337. break
  338. else:
  339. print("📋 无法找到标准日志文件,建议使用: docker logs trend-radar")
  340. except KeyboardInterrupt:
  341. print("\n👋 退出日志查看")
  342. except Exception as e:
  343. print(f"❌ 查看日志失败: {e}")
  344. print("💡 建议使用: docker logs trend-radar")
  345. def restart_supercronic():
  346. """重启supercronic进程"""
  347. print("🔄 重启supercronic...")
  348. print("⚠️ 注意: supercronic 是 PID 1,无法直接重启")
  349. # 检查当前 PID 1
  350. try:
  351. with open('/proc/1/cmdline', 'r') as f:
  352. pid1_cmdline = f.read().replace('\x00', ' ').strip()
  353. print(f" 🔍 当前 PID 1: {pid1_cmdline}")
  354. if "supercronic" in pid1_cmdline.lower():
  355. print(" ✅ PID 1 是 supercronic")
  356. print(" 💡 要重启 supercronic,需要重启整个容器:")
  357. print(" docker restart trend-radar")
  358. else:
  359. print(" ❌ PID 1 不是 supercronic,这是异常状态")
  360. print(" 💡 建议重启容器以修复问题:")
  361. print(" docker restart trend-radar")
  362. except Exception as e:
  363. print(f" ❌ 无法检查 PID 1: {e}")
  364. print(" 💡 建议重启容器: docker restart trend-radar")
  365. def start_webserver():
  366. """启动 Web 服务器托管 output 目录"""
  367. print(f"🌐 启动 Web 服务器 (端口: {WEBSERVER_PORT})...")
  368. print(f" 🔒 安全提示:仅提供静态文件访问,限制在 {WEBSERVER_DIR} 目录")
  369. # 检查是否已经运行
  370. if Path(WEBSERVER_PID_FILE).exists():
  371. try:
  372. with open(WEBSERVER_PID_FILE, 'r') as f:
  373. old_pid = int(f.read().strip())
  374. try:
  375. os.kill(old_pid, 0) # 检查进程是否存在
  376. print(f" ⚠️ Web 服务器已在运行 (PID: {old_pid})")
  377. print(f" 💡 访问: http://localhost:{WEBSERVER_PORT}")
  378. print(" 💡 停止服务: python manage.py stop_webserver")
  379. return
  380. except OSError:
  381. # 进程不存在,删除旧的 PID 文件
  382. os.remove(WEBSERVER_PID_FILE)
  383. except Exception as e:
  384. print(f" ⚠️ 清理旧的 PID 文件: {e}")
  385. try:
  386. os.remove(WEBSERVER_PID_FILE)
  387. except:
  388. pass
  389. # 检查目录是否存在
  390. if not Path(WEBSERVER_DIR).exists():
  391. print(f" ❌ 目录不存在: {WEBSERVER_DIR}")
  392. return
  393. try:
  394. # 启动 HTTP 服务器
  395. # 使用 --bind 绑定到 0.0.0.0 使容器内部可访问
  396. # 工作目录限制在 WEBSERVER_DIR,防止访问其他目录
  397. process = subprocess.Popen(
  398. [sys.executable, '-m', 'http.server', str(WEBSERVER_PORT), '--bind', '0.0.0.0'],
  399. cwd=WEBSERVER_DIR,
  400. stdout=subprocess.DEVNULL,
  401. stderr=subprocess.DEVNULL,
  402. start_new_session=True
  403. )
  404. # 等待一下确保服务器启动
  405. time.sleep(1)
  406. # 检查进程是否还在运行
  407. if process.poll() is None:
  408. # 保存 PID
  409. with open(WEBSERVER_PID_FILE, 'w') as f:
  410. f.write(str(process.pid))
  411. print(f" ✅ Web 服务器已启动 (PID: {process.pid})")
  412. print(f" 📁 服务目录: {WEBSERVER_DIR} (只读,仅静态文件)")
  413. print(f" 🌐 访问地址: http://localhost:{WEBSERVER_PORT}")
  414. print(f" 📄 首页: http://localhost:{WEBSERVER_PORT}/index.html")
  415. print(" 💡 停止服务: python manage.py stop_webserver")
  416. else:
  417. print(f" ❌ Web 服务器启动失败")
  418. except Exception as e:
  419. print(f" ❌ 启动失败: {e}")
  420. def stop_webserver():
  421. """停止 Web 服务器"""
  422. print("🛑 停止 Web 服务器...")
  423. if not Path(WEBSERVER_PID_FILE).exists():
  424. print(" ℹ️ Web 服务器未运行")
  425. return
  426. try:
  427. with open(WEBSERVER_PID_FILE, 'r') as f:
  428. pid = int(f.read().strip())
  429. try:
  430. # 尝试终止进程
  431. os.kill(pid, signal.SIGTERM)
  432. time.sleep(0.5)
  433. # 检查进程是否已终止
  434. try:
  435. os.kill(pid, 0)
  436. # 进程还在,强制杀死
  437. os.kill(pid, signal.SIGKILL)
  438. print(f" ⚠️ 强制停止 Web 服务器 (PID: {pid})")
  439. except OSError:
  440. print(f" ✅ Web 服务器已停止 (PID: {pid})")
  441. except OSError as e:
  442. if e.errno == 3: # No such process
  443. print(f" ℹ️ 进程已不存在 (PID: {pid})")
  444. else:
  445. raise
  446. # 删除 PID 文件
  447. os.remove(WEBSERVER_PID_FILE)
  448. except Exception as e:
  449. print(f" ❌ 停止失败: {e}")
  450. # 尝试清理 PID 文件
  451. try:
  452. os.remove(WEBSERVER_PID_FILE)
  453. except:
  454. pass
  455. def webserver_status():
  456. """查看 Web 服务器状态"""
  457. print("🌐 Web 服务器状态:")
  458. if not Path(WEBSERVER_PID_FILE).exists():
  459. print(" ⭕ 未运行")
  460. print(f" 💡 启动服务: python manage.py start_webserver")
  461. return
  462. try:
  463. with open(WEBSERVER_PID_FILE, 'r') as f:
  464. pid = int(f.read().strip())
  465. try:
  466. os.kill(pid, 0) # 检查进程是否存在
  467. print(f" ✅ 运行中 (PID: {pid})")
  468. print(f" 📁 服务目录: {WEBSERVER_DIR}")
  469. print(f" 🌐 访问地址: http://localhost:{WEBSERVER_PORT}")
  470. print(f" 📄 首页: http://localhost:{WEBSERVER_PORT}/index.html")
  471. print(" 💡 停止服务: python manage.py stop_webserver")
  472. except OSError:
  473. print(f" ⭕ 未运行 (PID 文件存在但进程不存在)")
  474. os.remove(WEBSERVER_PID_FILE)
  475. print(" 💡 启动服务: python manage.py start_webserver")
  476. except Exception as e:
  477. print(f" ❌ 状态检查失败: {e}")
  478. def show_help():
  479. """显示帮助信息"""
  480. help_text = """
  481. 🐳 TrendRadar 容器管理工具
  482. 📋 命令列表:
  483. run - 手动执行一次爬虫
  484. status - 显示容器运行状态
  485. config - 显示当前配置
  486. files - 显示输出文件
  487. logs - 实时查看日志
  488. restart - 重启说明
  489. start_webserver - 启动 Web 服务器托管 output 目录
  490. stop_webserver - 停止 Web 服务器
  491. webserver_status - 查看 Web 服务器状态
  492. help - 显示此帮助
  493. 📖 使用示例:
  494. # 在容器中执行
  495. python manage.py run
  496. python manage.py status
  497. python manage.py logs
  498. python manage.py start_webserver
  499. # 在宿主机执行
  500. docker exec -it trend-radar python manage.py run
  501. docker exec -it trend-radar python manage.py status
  502. docker exec -it trend-radar python manage.py start_webserver
  503. docker logs trend-radar
  504. 💡 常用操作指南:
  505. 1. 检查运行状态: status
  506. - 查看 supercronic 是否为 PID 1
  507. - 检查配置文件和关键文件
  508. - 查看 cron 调度设置
  509. 2. 手动执行测试: run
  510. - 立即执行一次新闻爬取
  511. - 测试程序是否正常工作
  512. 3. 查看日志: logs
  513. - 实时监控运行情况
  514. - 也可使用: docker logs trend-radar
  515. 4. 重启服务: restart
  516. - 由于 supercronic 是 PID 1,需要重启整个容器
  517. - 使用: docker restart trend-radar
  518. 5. Web 服务器管理:
  519. - 启动: start_webserver
  520. - 停止: stop_webserver
  521. - 状态: webserver_status
  522. - 访问: http://localhost:8080
  523. """
  524. print(help_text)
  525. def main():
  526. if len(sys.argv) < 2:
  527. show_help()
  528. return
  529. command = sys.argv[1]
  530. commands = {
  531. "run": manual_run,
  532. "status": show_status,
  533. "config": show_config,
  534. "files": show_files,
  535. "logs": show_logs,
  536. "restart": restart_supercronic,
  537. "start_webserver": start_webserver,
  538. "stop_webserver": stop_webserver,
  539. "webserver_status": webserver_status,
  540. "help": show_help,
  541. }
  542. if command in commands:
  543. try:
  544. commands[command]()
  545. except KeyboardInterrupt:
  546. print("\n👋 操作已取消")
  547. except Exception as e:
  548. print(f"❌ 执行出错: {e}")
  549. else:
  550. print(f"❌ 未知命令: {command}")
  551. print("运行 'python manage.py help' 查看可用命令")
  552. if __name__ == "__main__":
  553. main()