在绿联 NAS 上部署 AI 选题生成器:new-api + MiniMax M3 + 飞书自动推送
在绿联 NAS 上部署 AI 选题生成器:new-api + MiniMax M3 + 飞书自动推送
每天早上 8:00 自动生成 5 个爆款选题,自动落盘到 Markdown,自动推送到飞书群。起床就能在手机上看到当天的内容灵感。
目录
项目目标
部署完成后,你的绿联 NAS 会自动帮你做三件事:
- ⏰ 每天 8:00 自动起床 —— 跑 Python 脚本调用 MiniMax M3,生成 5 个内容选题
- 📁 自动落盘 —— 结果保存到
/volume1/docker/scripts/topics/output/daily-YYYY-MM-DD.md - 📲 自动推送飞书 —— 通过飞书自定义机器人,把当日选题卡片发到指定群
支持的 6 个内容模板:
| 模板 | 用途 |
|---|---|
topics | 5 个爆款选题(公众号/小红书通用) |
article | 公众号长文(2000 字) |
redbook | 小红书爆款笔记 |
zhihu | 知乎高赞回答 |
douyin | 抖音 60 秒脚本 |
podcast | 播客大纲 |
整体架构
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ systemd │ 8:00 │ topics.py │ ───→ │ new-api │ ───→ MiniMax M3
│ timer │─────→│ (生成选题) │ │ (4152:3000) │
└──────────────┘ └──────┬───────┘ └──────────────┘
│ Markdown
↓
┌──────────────┐
│ output/ │
│ daily-*.md │
└──────┬───────┘
│ 读取
↓
┌──────────────┐ ┌──────────────┐
│ push_feishu │ ───→ │ 飞书自定义机器人│ ───→ 手机
│ .py │ │ (webhook) │
└──────────────┘ └──────────────┘
所有组件都跑在同一台绿联 NAS 上,没有外部服务器依赖。
前置准备
| 项目 | 说明 |
|---|---|
| 绿联 NAS | 本教程用 DXP6800PRO(其他型号流程一样) |
| new-api 容器 | 已通过 Docker 部署(端口映射 4152:3000) |
| MiniMax M3 模型的 API key | 用于 new-api 配置渠道 |
| 飞书账号 | 创建群和自定义机器人用 |
| Mac 电脑 | 用于 SSH 登录 NAS |
完整部署步骤
步骤 1:开启 NAS 的 SSH 访问
绿联 NAS 默认不开启 SSH,需要先在控制面板里打开。
打开浏览器,访问 NAS 管理后台(通常是 http://NAS内网IP),进入 控制面板:

左侧菜单选择 终端机:

勾选 启用 SSH,端口保持默认 22,自动关闭时间按需选择(推荐 6 小时或更长),点击 应用 保存。
⚠️ 开启 SSH 等于给 NAS 开了"后门",强烈建议:
- NAS 管理员账号使用强密码
- 在路由器上设置 NAS 仅内网可访问(不要开公网 SSH)
- 启用 NAS 自带的"自动封锁"功能(控制面板 → 安全性 → 账户安全)
步骤 2:Mac 终端 SSH 登录 NAS
打开 Mac 终端(Terminal.app 或 iTerm2):

执行 SSH 登录命令:
ssh 你的用户名@NAS内网IP
# 例如: ssh 你的用户名@192.1.1.1(你的nas的ip)
# 密码是 NAS 管理员密码
绿联 NAS 默认用户名不是
root,是你创建的管理员账号(教程里用zs2025作为示例)。
登录后切到 root(后续所有操作需要 root 权限):
sudo -i
# 输入 NAS 管理员密码
看到 # 提示符就说明已经在 root 模式下了。
步骤 3:建目录 + 安装 Python 依赖
# 建工作目录
mkdir -p /volume1/docker/scripts/topics(用自己的路径)
cd /volume1/docker/scripts/topics
# 装 Python(绿联 DXP 系统 apt 经常有依赖冲突,先尝试直接装)
apt update && apt install -y python3 python3-pip python3-venv
如果 apt 装包报依赖错误,先尝试修复:
apt --fix-broken install -y
apt install -y python3-venv python3-pip
📌 为什么用
/volume1/docker/scripts/而不是/opt/?
/volume1是绿联 NAS 的用户数据挂载点,重装系统、迁移数据时不会丢失。/opt是系统目录,可能会被清空。
继续建虚拟环境:
# 创建 Python 虚拟环境
python3 -m venv venv
source venv/bin/activate
# 安装 requests 库(调用 API 用)
pip install requests
看到 Successfully installed requests-2.x.x 就成功了。后续所有 Python 命令都要先 source venv/bin/activate 激活虚拟环境。
部署完成后,打开 NAS 文件管理能看到这样的目录结构:

步骤 4:new-api 后台配置 MiniMax M3 渠道
这一步是最容易漏掉的 —— new-api 光跑起来没用,还要在后台加渠道,否则 Python 调过去会 404。
- 浏览器访问
http://NAS内网IP:4152 - 默认账号
admin / 123456(第一次登录立刻改密码) - 进入 渠道管理 → 添加渠道:
- 类型:选
MiniMax或OpenAI 兼容 - 名称:随便起,比如
MiniMax-M3 - 密钥:填你手头的 MiniMax 官方 API key
- 模型:先在 new-api 模型列表里查准确名字(可能是
MiniMax-M3或MiniMax/M3)
- 类型:选
- 保存后点 测试连通性,看到绿色"通过"再继续
- 进入 令牌管理 → 添加令牌:
- 名称:
topics-script - 额度:随便填,比如 100
- 模型:选刚加的 channel
- 名称:
- 复制生成的 sk-xxx 令牌 —— 这个才是 topics.py 要用的 API_KEY(不是你 MiniMax 那个)
⚠️ 划重点:new-api 的令牌和 MiniMax 官方 key 是两码事,别搞混了。
步骤 5:部署 topics.py(核心脚本)
接下来部署核心脚本 topics.py。考虑到 SSH 终端复制粘贴大段代码容易出乱码(特别是中文字符 + 终端宽度问题),我用 base64 单行命令 的方式部署。
完整复制下面这一整行(注意 base64 字符串很长,复制时务必完整,建议复制后先在文本编辑器里看末尾有没有 ==):
cd /volume1/docker/scripts/topics && source venv/bin/activate && echo 'IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoiIiIKdG9waWNzLnB5IC0g6YCJ6aKY55Sf5oiQ5Zmo77yITWluaU1heCBNMyB2aWEgbmV3LWFwae+8iQrnlKjms5U6IHB5dGhvbiB0b3BpY3MucHkgPHRlbXBsYXRlPiAiPHRvcGljPiIgWy1vIG91dHB1dC5tZF0K5qih5p2/OiB0b3BpY3MgfCBhcnRpY2xlIHwgcmVkYm9vayB8IHpoaWh1IHwgZG91eWluIHwgcG9kY2FzdAoiIiIKaW1wb3J0IG9zLCBzeXMsIGFyZ3BhcnNlLCByZXF1ZXN0cwpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKCmVudl9maWxlID0gUGF0aChfX2ZpbGVfXykucGFyZW50IC8gIi5lbnYiCmlmIGVudl9maWxlLmV4aXN0cygpOgogICAgZm9yIGxpbmUgaW4gZW52X2ZpbGUucmVhZF90ZXh0KCkuc3BsaXRsaW5lcygpOgogICAgICAgIGlmIGxpbmUuc3RyaXAoKSBhbmQgbm90IGxpbmUuc3RhcnRzd2l0aCgiIyIpIGFuZCAiPSIgaW4gbGluZToKICAgICAgICAgICAgaywgdiA9IGxpbmUuc3BsaXQoIj0iLCAxKQogICAgICAgICAgICBvcy5lbnZpcm9uLnNldGRlZmF1bHQoay5zdHJpcCgpLCB2LnN0cmlwKCkuc3RyaXAoJyInKS5zdHJpcCgiJyIpKQoKQVBJX0JBU0UgPSBvcy5lbnZpcm9uWyJBUElfQkFTRSJdLnJzdHJpcCgiLyIpCkFQSV9LRVkgPSBvcy5lbnZpcm9uWyJBUElfS0VZIl0KTU9ERUwgPSBvcy5lbnZpcm9uLmdldCgiTU9ERUwiLCAiTWluaU1heC1NMyIpCgpURU1QTEFURVMgPSB7CiAgICAidG9waWNzIjogKCI15Liq54iG5qy+6YCJ6aKYIiwKICAgICAgICAi5L2g5piv6aG257qn5YaF5a65562W55Wl5biI77yM5pOF6ZW/5Li65oqA5pyv5Y2a5Li76K6+6K6h5YWs5LyX5Y+36YCJ6aKY44CCIiwKICAgICAgICAi5Li65Li76aKY44CMe3RvcGljfeOAjeeUn+aIkDXkuKrniIbmrL7pgInpopjvvIzmr4/kuKrlkKvvvJrjgJDmoIfpopjjgJExNS0yNeWtl+OAkOmSqeWtkOOAkTHlj6XlvIDlpLTjgJDnsbvlnovjgJHnl5vngrkv5Y+N5beuL+W5sui0py/mlYXkuosv5LqJ6K6uIiksCiAgICAiYXJ0aWNsZSI6ICgi5YWs5LyX5Y+36ZW/5paHIiwKICAgICAgICAi5L2g5piv5YaZ6L+HNTAwK+evhzEwVyvnmoTlhazkvJflj7fkvZzogIXjgIIiLAogICAgICAgICLkuLrkuLvpopjjgIx7dG9waWN944CN5YaZ5LiA56+HMjAwMOWtl+aWh+eroO+8muW8gOWktDHkuKrmlYXkuovpkqnlrZDvvIwzLTXkuKrlsI/oioLluKblsI/moIfpopjvvIznu5PlsL7nu5nlj6/miafooYzmuIXljZXjgIIiKSwKICAgICJyZWRib29rIjogKCLlsI/nuqLkuabniIbmrL4iLAogICAgICAgICLkvaDmmK/lsI/nuqLkuablpLTpg6jljZrkuLvvvIzmh4LmtYHph4/lr4bnoIHjgIIiLAogICAgICAgICLkuLrkuLvpopjjgIx7dG9waWN944CN5YaZMeevh+Wwj+e6ouS5pueslOiusO+8muagh+mimDE15a2X5YaFK+ato+aWhzMwMC01MDDlrZcrNS045Liq5qCH562+44CCIiksCiAgICAiemhpaHUiOiAoIuefpeS5jumrmOi1niIsCiAgICAgICAgIuS9oOaYr+efpeS5juS4h+i1nuetlOS4u++8jOmAu+i+keS4peWvhuOAgiIsCiAgICAgICAgIuS4uuS4u+mimOOAjHt0b3BpY33jgI3lhpkx5Liq6auY6LWe5Zue562U77ya5byA5aS05Lqu6KeC54K5K+S4remXtDMtNOS4quiuuuaNruW4puahiOS+iyvnu5PlsL7ljYfljY7jgIIiKSwKICAgICJkb3V5aW4iOiAoIuaKlumfs+iEmuacrCIsCiAgICAgICAgIuS9oOaYr+aKlumfs+eIhuasvuiEmuacrOS9nOiAhe+8jDYw56eS6ZKp5Lq65Yiw5bqV44CCIiwKICAgICAgICAi5Li65Li76aKY44CMe3RvcGljfeOAjeWGmTHkuKo2MOenkuiEmuacrO+8mjAtM+enkuW8uumSqeWtkC80LTUw56eS5LiJ54K56YWN5qGI5L6LLzUxLTYw56eS5Y+N6L2sK+aMh+S7pOOAgiIpLAogICAgInBvZGNhc3QiOiAoIuaSreWuouWkp+e6siIsCiAgICAgICAgIuS9oOaYr+aSreWuouS4u+eQhuS6uu+8jOaHgua3seW6puWvueivneOAgiIsCiAgICAgICAgIuS4uuS4u+mimOOAjHt0b3BpY33jgI3orr7orqEx5pyfMzDliIbpkp/mkq3lrqLlpKfnurLvvJrkuLvor53popgrM+S4quWIhuivnemimCvmr4/kuKrphY3orqjorrrpl67popgr57uT5bC+6YeR5Y+l44CCIiksCn0KCmRlZiBjYWxsX2xsbShtZXNzYWdlcywgdGltZW91dD02MCk6CiAgICB1cmwgPSBmIntBUElfQkFTRX0vY2hhdC9jb21wbGV0aW9ucyIKICAgIGhlYWRlcnMgPSB7IkF1dGhvcml6YXRpb24iOiBmIkJlYXJlciB7QVBJX0tFWX0iLCAiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24ifQogICAgcGF5bG9hZCA9IHsibW9kZWwiOiBNT0RFTCwgIm1lc3NhZ2VzIjogbWVzc2FnZXMsICJ0ZW1wZXJhdHVyZSI6IDAuOH0KICAgIHIgPSByZXF1ZXN0cy5wb3N0KHVybCwgaGVhZGVycz1oZWFkZXJzLCBqc29uPXBheWxvYWQsIHRpbWVvdXQ9dGltZW91dCkKICAgIHIucmFpc2VfZm9yX3N0YXR1cygpCiAgICByZXR1cm4gci5qc29uKClbImNob2ljZXMiXVswXVsibWVzc2FnZSJdWyJjb250ZW50Il0KCmRlZiBtYWluKCk6CiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcihkZXNjcmlwdGlvbj0i6YCJ6aKY55Sf5oiQ5ZmoIikKICAgIHBhcnNlci5hZGRfYXJndW1lbnQoInRlbXBsYXRlIiwgaGVscD0i5qih5p2/5ZCNOiB0b3BpY3N8YXJ0aWNsZXxyZWRib29rfHpoaWh1fGRvdXlpbnxwb2RjYXN0IikKICAgIHBhcnNlci5hZGRfYXJndW1lbnQoInRvcGljIiwgaGVscD0i5Li76aKY5YWz6ZSu6K+NIikKICAgIHBhcnNlci5hZGRfYXJndW1lbnQoIi1vIiwgIi0tb3V0cHV0IiwgaGVscD0i6L6T5Ye65YiwIE1hcmtkb3duIOaWh+S7tiIpCiAgICBhcmdzID0gcGFyc2VyLnBhcnNlX2FyZ3MoKQoKICAgIGlmIGFyZ3MudGVtcGxhdGUgbm90IGluIFRFTVBMQVRFUzoKICAgICAgICBwcmludChmIueUqOazlTogcHl0aG9uIHRvcGljcy5weSA8eyd8Jy5qb2luKFRFTVBMQVRFUy5rZXlzKCkpfT4gXCI85Li76aKYPlwiIFstbyBvdXRwdXQubWRdIikKICAgICAgICBzeXMuZXhpdCgxKQoKICAgIG5hbWUsIHN5c19wLCB1c3JfcCA9IFRFTVBMQVRFU1thcmdzLnRlbXBsYXRlXQogICAgcHJpbnQoZiLnm67moIcge25hbWV9IHwg5Li76aKYOiB7YXJncy50b3BpY31cbiIpCiAgICByZXN1bHQgPSBjYWxsX2xsbShbCiAgICAgICAgeyJyb2xlIjogInN5c3RlbSIsICJjb250ZW50Ijogc3lzX3B9LAogICAgICAgIHsicm9sZSI6ICJ1c2VyIiwgImNvbnRlbnQiOiB1c3JfcC5mb3JtYXQodG9waWM9YXJncy50b3BpYyl9LAogICAgXSkKICAgIHByaW50KHJlc3VsdCkKCiAgICBpZiBhcmdzLm91dHB1dDoKICAgICAgICBmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQogICAgICAgIHBhdGggPSBQYXRoKGFyZ3Mub3V0cHV0KQogICAgICAgIHBhdGgucGFyZW50Lm1rZGlyKHBhcmVudHM9VHJ1ZSwgZXhpc3Rfb2s9VHJ1ZSkKICAgICAgICB3aXRoIG9wZW4ocGF0aCwgImEiLCBlbmNvZGluZz0idXRmLTgiKSBhcyBmOgogICAgICAgICAgICBmLndyaXRlKGYiIyB7YXJncy50ZW1wbGF0ZX0gfCB7YXJncy50b3BpY30gfCB7ZGF0ZXRpbWUubm93KCkuc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNJyl9XG5cbiIpCiAgICAgICAgICAgIGYud3JpdGUocmVzdWx0ICsgIlxuIikKICAgICAgICBwcmludChmIuW3suiQveebmDoge3BhdGh9IikKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBtYWluKCkK' | base64 -d > topics.py
chmod +x topics.py
# 验证文件写对了
head -3 topics.py
python3 -c "import ast; ast.parse(open('topics.py').read()); print('语法 OK')"
预期输出:
#!/usr/bin/env python3
"""
topics.py - 选题生成器(MiniMax M3 via new-api)
---
语法 OK
📌 为什么用 base64 而不是直接 nano 编辑?
SSH 终端复制粘贴大段中文代码经常出问题:换行丢失、字符截断、emoji 被截掉。用 base64 编码后整段代码变成 ASCII 单行,可以稳定复制。
📌 为什么用 argparse 而不是 sys.argv 解析
-o?
argparse 自带帮助文档(--help)和错误提示,对后期维护更友好。
步骤 6:部署 push_feishu.py(飞书推送)
同样用 base64 方式部署飞书推送脚本(这个版本我简化到 30 行,更容易复制):
cd /volume1/docker/scripts/topics
echo 'IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgb3MsIHN5cywgcmVxdWVzdHMKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmUgPSBQYXRoKF9fZmlsZV9fKS5wYXJlbnQgLyAiLmVudiIKaWYgZS5leGlzdHMoKToKICAgIGZvciBsIGluIGUucmVhZF90ZXh0KCkuc3BsaXRsaW5lcygpOgogICAgICAgIGlmICI9IiBpbiBsIGFuZCBub3QgbC5zdGFydHN3aXRoKCIjIik6CiAgICAgICAgICAgIGssIHYgPSBsLnNwbGl0KCI9IiwgMSkKICAgICAgICAgICAgb3MuZW52aXJvbi5zZXRkZWZhdWx0KGsuc3RyaXAoKSwgdi5zdHJpcCgpLnN0cmlwKCciJykuc3RyaXAoIiciKSkKVyA9IG9zLmVudmlyb24uZ2V0KCJGRUlTSFVfV0VCSE9PSyIsICIiKQpkZWYgcHVzaChjLCB0KToKICAgIGlmIG5vdCBXOgogICAgICAgIHJldHVybiBwcmludCgiWCBGRUlTSFVfV0VCSE9PSyDmnKrphY3nva4iKQogICAgaWYgbGVuKGMpID4gMjUwMDoKICAgICAgICBjID0gY1s6MjUwMF0gKyAiXG5cbi4uLijlrozmlbTop4Egb3V0cHV0L2RhaWx5LSoubWQpIgogICAgcCA9IHsibXNnX3R5cGUiOiAiaW50ZXJhY3RpdmUiLCAiY2FyZCI6IHsiaGVhZGVyIjogeyJ0aXRsZSI6IHsidGFnIjogInBsYWluX3RleHQiLCAiY29udGVudCI6IHR9fSwgImVsZW1lbnRzIjogW3sidGFnIjogIm1hcmtkb3duIiwgImNvbnRlbnQiOiBjfV19fQogICAgciA9IHJlcXVlc3RzLnBvc3QoVywganNvbj1wLCB0aW1lb3V0PTEwKQogICAgb2sgPSByLnN0YXR1c19jb2RlID09IDIwMCBhbmQgci5qc29uKCkuZ2V0KCJTdGF0dXNDb2RlIiwgci5qc29uKCkuZ2V0KCJjb2RlIiwgMSkpID09IDAKICAgIHByaW50KCJPSyDmjqjpgIHmiJDlip8iIGlmIG9rIGVsc2UgZiJYIOWksei0pToge3IudGV4dH0iKQppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbSA9IFBhdGgoc3lzLmFyZ3ZbMV0pCiAgICBjID0gbS5yZWFkX3RleHQoZW5jb2Rpbmc9InV0Zi04IikKICAgIHQgPSAi5LuK5pel6YCJ6aKYIgogICAgZm9yIGwgaW4gYy5zcGxpdCgiXG4iKToKICAgICAgICBpZiBsLnN0YXJ0c3dpdGgoIiMgIik6CiAgICAgICAgICAgIHggPSBsWzI6XS5zdHJpcCgpCiAgICAgICAgICAgIGlmIHg6CiAgICAgICAgICAgICAgICB0ID0gIuS7iuaXpSAiICsgeFs6MzBdCiAgICAgICAgICAgIGJyZWFrCiAgICBwdXNoKGMsIHQpCg==' | base64 -d > push_feishu.py
chmod +x push_feishu.py
# 验证
head -3 push_feishu.py
步骤 7:创建 run_daily.sh 整合脚本
写一个 Shell wrapper 把"生成选题 + 推送飞书"两个步骤打包。用 $(date +%Y-%m-%d) 让文件名带日期:
cat > /volume1/docker/scripts/topics/run_daily.sh <<'EOF'
#!/bin/bash
set -e
cd /volume1/docker/scripts/topics
FILE="output/daily-$(date +%Y-%m-%d).md"
./venv/bin/python topics.py topics "今日内容选题" -o "$FILE"
./venv/bin/python push_feishu.py "$FILE"
EOF
chmod +x /volume1/docker/scripts/topics/run_daily.sh
⚠️
set -e很重要:任何一步失败就立刻退出,不会带着"没推送成功"还继续跑下一次。
⚠️ 不要用 systemd 的
ExecStart=... daily-%Y-%m-%d.md—— systemd 不会展开日期占位符,文件名会变成字面的daily-%Y-%m-%d.md。用 shell wrapper 才是正解。
步骤 8:配置文件 .env
.env 文件存放所有敏感配置(API key、webhook URL 等),记得加 chmod 600:
cd /volume1/docker/scripts/topics
echo 'API_BASE="http://127.0.0.1:4152/v1"' > .env
echo 'API_KEY="sk-你在new-api生成的令牌"' >> .env
echo 'MODEL="MiniMax-M3"' >> .env
echo 'FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/你的真实URL"' >> .env
chmod 600 .env
cat .env
⚠️ API_KEY 和 FEISHU_WEBHOOK 不要贴到聊天记录里!这两个是访问凭证,泄露后别人可以:
- API_KEY:调用你的 new-api 配额(产生费用)
- FEISHU_WEBHOOK:往你的群乱发消息(骚扰)
预期输出(注意:key 必须是真值,不是占位符):
API_BASE="http://127.0.0.1:4152/v1"
API_KEY="sk-rgxsuLVWJ7CXf9E9NEJ9RALyVnK0nYGYQq5hQyvTIWHF0rTC"
MODEL="MiniMax-M3"
FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/fb2b9118-xxxx"
步骤 9:飞书创建自定义机器人
- 飞书 App 创建一个群(最少 2 人,可以拉小号或者用"文件传输助手"代替)
- 群右上角 → 设置 → 群机器人 → 添加机器人 → 自定义机器人
- 名称:
每日选题(随便起) - 头像:随便选
- 安全设置:先不勾选任何校验(开发测试阶段简单点)
- 复制生成的 webhook URL(不要贴到任何聊天里)
- 把 URL 填到步骤 8 的
.env文件里
📌 后续如果要更严的安全,可以加"签名校验",飞书后台会生成一个 secret,配合签名算法生成校验头。
步骤 10:测试选题生成 + 飞书推送
先单独测试选题生成:
cd /volume1/docker/scripts/topics
source venv/bin/activate
python topics.py topics "MaxKB 训练"
预期输出:
目标 5个爆款选题 | 主题: MaxKB 训练
# 🎯「MaxKB 训练」5大爆款选题
...
再测试带落盘:
mkdir -p output
python topics.py topics "MaxKB 训练" -o output/test.md
ls -la output/
cat output/test.md
最后测试飞书推送:
./venv/bin/python push_feishu.py output/test.md
预期输出:OK 推送成功,同时飞书群立即收到一条带标题和内容的卡片消息。
如果推送失败,常见错误码见末尾 E 类:飞书推送问题。
步骤 11:配置 systemd 定时任务
systemd 是 Linux 系统自带的服务管理器,用它来实现"每天 8:00 自动跑"。
创建 service 文件(定义"做什么"):
cat > /etc/systemd/system/topics.service <<'EOF'
[Unit]
Description=Daily topics generation
[Service]
Type=oneshot
WorkingDirectory=/volume1/docker/scripts/topics
ExecStart=/volume1/docker/scripts/topics/run_daily.sh
User=root
EOF
创建 timer 文件(定义"什么时候跑"):
cat > /etc/systemd/system/topics.timer <<'EOF'
[Unit]
Description=Run topics generation daily at 08:00
[Timer]
OnCalendar=*-*-* 08:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
📌
Persistent=true很重要:开机补跑。如果 NAS 在 8:00 是关机状态,开机后会立即补跑一次,不会漏掉。
启用定时器:
systemctl daemon-reload
systemctl enable topics.timer
systemctl start topics.timer
步骤 12:验证定时器 + 手动触发
查看定时器状态:
systemctl list-timers topics.timer
预期输出:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-07-21 08:00:00 CST 8h left - - topics.timer topics.service
立即手动触发一次(不等明天 8 点):
systemctl start topics.service
查看执行日志:
systemctl status topics.service
journalctl -u topics.service --no-pager -n 20
📌
systemctl status默认用less分页,按q退出。
预期看到:
Active: inactive (dead) since ...
TriggeredBy: ● topics.timer
Process: ... ExecStart=... (code=exited, status=0/SUCCESS)
CPU: 306ms
并且飞书群立即收到当天选题卡片。
项目文件结构
部署完成后的目录结构:
/volume1/docker/scripts/topics/
├── venv/ # Python 虚拟环境
│ └── bin/python
├── topics.py # 选题生成核心脚本(70 行)
├── push_feishu.py # 飞书推送脚本(30 行)
├── run_daily.sh # 每日执行入口
├── .env # 配置文件(API key、webhook URL)
│ # 权限 600,仅 root 可读
└── output/ # 选题历史
├── daily-2026-07-20.md
├── daily-2026-07-21.md
└── ...
系统级文件:
/etc/systemd/system/
├── topics.service # 定义执行内容
└── topics.timer # 定义执行时间
常见问题(按类型归类)
部署过程踩了十几个坑,把它们按类型归类,方便后续遇到类似问题对照。
A 类:环境/Python 依赖问题
A1. ensurepip is not available / python3-venv not found
现象:建 venv 报 ensurepip 缺失。
原因:绿联 DXP 系统的 apt 源依赖混乱(部分包版本号是 Ubuntu 的,混在 Debian 里)。
修法:
apt --fix-broken install -y
apt install -y python3-venv python3-pip
如果还装不上,退路方案(绕过 apt):
# 手动装 pip
curl -sS https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py
python3 /tmp/get-pip.py --break-system-packages
# 装 requests 到用户目录
python3 -m pip install --break-system-packages --user requests
后续跑脚本不用 source venv,直接 python3 即可。
A2. ModuleNotFoundError: No module named 'requests'
现象:跑 python topics.py 报找不到 requests。
原因:没激活 venv,或者 venv 没装 requests。
修法:
cd /volume1/docker/scripts/topics
source venv/bin/activate
pip install requests
每次开新终端都要先 source venv/bin/activate,提示符从 $ 变成 (venv) $ 才算激活。
B 类:文件写入/编码问题
B1. SyntaxError: invalid non-printable character U+0002
现象:base64 解码出来的 Python 文件包含奇怪的不可见字符,Python 解析失败。
原因:base64 字符串在终端复制粘贴时被截断,引入 ASCII 控制字符。
修法:
- 重新生成 base64 字符串(用更短的代码版本)
- 复制后在文本编辑器里先看一下,确保字符串末尾有
==(base64 padding) - 如果字符串太长被终端截断,分两段 base64 写(避免单行过长)
B2. SyntaxError: Non-UTF-8 code starting with '\xbc'
现象:Python 文件编码错误。
原因:base64 解码失败(输入不完整),文件只写了一半或写入了 binary 垃圾。
修法:
rm -f push_feishu.py
# 重新跑 base64 部署命令
B3. heredoc 复制粘贴代码出现乱码
现象:用 cat > file <<'EOF' ... EOF 写大段代码,结果文件里出现 chmod +x topics.pymain__":content":... 这种乱序拼接的代码。
原因:SSH 终端粘贴 heredoc 时,标记行(EOF)可能被截断,代码块不完整。
修法:
- 不要用 heredoc 写大段代码,改用 base64 方式
- 如果必须用 heredoc,复制时确保最后一行
EOF没丢
C 类:权限/用户问题
C1. Operation not permitted / Permission denied
现象:建文件、加权限操作时失败。
原因:当前是普通用户(如 zs2025),不是 root。绿联 NAS 默认 SSH 登录后是普通用户身份。
修法:
sudo -i
# 输入 NAS 管理员密码
# 看到 # 提示符就是 root
切到 root 后所有目录都能读写。教程里所有命令默认在 root 下执行。
C2. mv: overwrite '.env'? 卡住
现象:mv 命令被问是否覆盖文件。
原因:系统有 alias mv='mv -i' 别名(-i 表示交互式询问)。
修法:
# 方式 1:手动输入 y 回车
# 方式 2:强制覆盖
mv -f .env.tmp .env
D 类:API key / new-api 渠道问题
D1. Connection refused
现象:调用 new-api 时连接被拒。
原因:new-api 容器没启动 / 端口不对 / 防火墙拦截。
修法:
# 1. 测本机访问
curl http://127.0.0.1:4152
# 2. 测容器访问(如果 127.0.0.1 不通)
docker ps | grep new-api
docker logs 容器名
D2. 401 Unauthorized
现象:new-api 返回 401。
原因:API_KEY 错了 / 令牌被删了。
修法:
- 去 new-api 后台 → 令牌管理 → 确认令牌还在
- 重新复制 sk-xxx,更新 .env
- 如果 .env 里有两行
API_KEY=,只保留真值那行
D3. 404 Not Found
现象:new-api 返回 404。
原因:MODEL 名不对(new-api 找不到这个模型)。
修法:
- 去 new-api 后台 → 渠道管理 → 看你配置的模型实际叫什么
- 改
.env里的MODEL=字段 - 常见模型名格式:
MiniMax-M3、MiniMax/M3、gpt-4o-mini
D4. 跑通了但生成内容是乱码 / 跟主题无关
现象:选题生成出来了,但内容跟主题没关系。
原因:API_BASE 指向了错误的渠道(比如指向了别人的 key)。
修法:确认 new-api 后台渠道管理 → "测试连通性"通过。
E 类:飞书推送问题
E1. code:19001, msg: param invalid: incoming webhook access token invalid
现象:推送飞书时返回 token invalid。
原因:.env 里的 FEISHU_WEBHOOK 是占位符(https://...你的真实URL)而不是真 URL。
修法:
cd /volume1/docker/scripts/topics
# 1. 删占位符那行
sed -i '/你的真实URL/d' .env
# 2. 追加真 URL
echo 'FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/真UUID"' >> .env
cat .env
E2. code:19002, msg: webhook out of limit
现象:飞书返回频率限制。
原因:飞书自定义机器人有频率限制(每分钟最多 100 次)。
修法:检查是否有循环触发;如果正常推送看到这个错,等 1 分钟重试。
E3. 飞书群收不到消息
现象:脚本返回 OK 推送成功,但群没收到。
修法:
- 检查群是不是把机器人消息折叠了
- 在飞书群右上角 → 设置 → 消息管理 → 看"机器人"分组
- 如果 webhook 是新的,第一次推送要等 1-2 分钟才到(飞书有缓存)
E4. 消息内容超过 2500 字被截断
现象:长文只推了一半。
原因:飞书卡片消息体上限 2500 字。
修法:push_feishu.py 里有 if len(c) > 2500: c = c[:2500] + "...(完整见 output/daily-*.md)",会自动截断。要看完整版就上 NAS 看文件。
F 类:systemd 定时任务问题
F1. systemctl start topics.service 后没反应
现象:手动触发 service 没有任何输出。
原因:可能 service 配置错了,或者 shell wrapper 路径不对。
修法:
# 查看详细日志
journalctl -u topics.service -n 30
F2. 定时器不触发 / 错过时间
现象:8:00 过了但没跑。
原因:NAS 关机了 / 系统时间不对。
修法:
- 确认 NAS 没在 8:00 关机
timedatectl看系统时间Persistent=true已经保证开机补跑
F3. service 报 code=exited, status=2/INVALIDARGUMENT
现象:service 启动失败,退出码 2。
原因:service 文件里的 ExecStart 路径写错了。
修法:
# 检查文件实际路径
ls -la /volume1/docker/scripts/topics/run_daily.sh
cat /etc/systemd/system/topics.service
# 改完后
systemctl daemon-reload
F4. 文件名变成 daily-%Y-%m-%d.md 字面字符串
现象:生成了字面意义的文件名,没按日期展开。
原因:systemd 的 ExecStart 不支持 %Y/%m/%d 日期占位符展开。
修法:用 shell wrapper 包一层(本教程方案),让 $(date +%Y-%m-%d) 在 bash 里展开。
G 类:安全/密钥泄露问题
G1. 复制 API_KEY 到聊天记录
现象:为了让人帮忙排查,把 sk-xxx 贴到了 IM 聊天里。
风险:聊天记录可能持久化、备份、被搜索到。一旦泄露:
- 别人可以用你的 new-api 配额(产生费用)
- 别人可以冒充你调用模型
修法:
- 立刻去 new-api 后台删除该令牌,重新生成
- 用
sed -i 's|旧key|新key|' .env替换 - 测试确认
python topics.py topics "test"能跑通 - 以后排查问题时用
cat .env | sed 's|sk-.*|sk-***已隐藏***|'这种脱敏方式贴
G2. 复制 FEISHU_WEBHOOK 到聊天记录
现象:把飞书 webhook URL 贴到 IM 里。
风险:别人可以往你的群乱发消息(钓鱼、色情、垃圾广告)。没有财务风险但很烦。
修法:
- 去飞书群 → 设置 → 群机器人 → 删除该机器人 → 重新添加
- 新 URL 替换 .env 里的
- 以后排查问题时只贴 webhook 的 UUID 后半段(如
.../hook/xxxx-abcd1234),不全贴
G3. read -s -p "提示文案 sk-真实key" 变量 误用
现象:用 read -s 安全输入时,把 key 写到了 prompt 字符串里(-s 只隐藏用户输入,但 prompt 本身会显示)。
风险:prompt 字符串会原样显示在终端 / SSH 日志里。
修法:用 read -s -p "请输入 key: " VAR,prompt 里只放占位提示(如"请输入 key:"),不把真 key 写进 prompt。
进阶玩法
部署完基础功能后,可以加这些增强:
| 功能 | 实现思路 |
|---|---|
| 🔄 多主题轮换 | 改 run_daily.sh 用 $(date +%u) 映射周一~周日不同主题 |
| 🧠 多模板日更 | topics.py 调用多个模板(选题 + 长文开头),合并推送 |
| 📊 历史库管理 | 按月归档 output/2026-07/,加个 Python 脚本生成月度索引 |
| 🌐 Web 预览页 | 启动一个简单的 HTTP server(python3 -m http.server),在内网浏览器看历史 |
| 📲 多群推送 | .env 加多个 FEISHU_WEBHOOK_2、FEISHU_WEBHOOK_3,循环推 |
| 🔒 签名校验 | 飞书机器人后台启用"签名校验",push_feishu.py 加签名生成逻辑 |
| 📈 用量统计 | 每天记录 token 用量到 SQLite,定期出用量报告 |
写在最后
这套方案的核心思路是:把 NAS 变成一个永不关机的 AI 助理。
- 每天早上 8:00 自动醒来
- 自动写好当天内容选题
- 自动发到你手机上
- 你只需要在通勤路上扫一眼,挑一个最想写的
整个项目从 0 到跑通大约 25-40 分钟,遇到坑主要是绿联 NAS 的 apt 依赖混乱(要 --fix-broken)和 SSH 终端复制粘贴大段代码(要用 base64)。
几个最重要的安全习惯:
- API key 永远不要贴聊天
- 任何部署完成第一时间 rotate 默认密码(new-api 的 admin/123456)
- SSH 只开内网访问
- .env 文件保持 chmod 600
希望这篇教程能帮你少踩几个坑。如果有改进建议,欢迎留言交流。
📅 部署时间:2026-07-20
🛠️ 硬件:绿联 DXP6800PRO
🤖 模型:MiniMax M3 via new-api
📲 推送:飞书自定义机器人

.png)
-bpoi.png)
