feat: Phase 1 添加 GHA 定时、run-daily.sh 与 Docker 部署
让早报可在无本地服务器环境下定时生成并推送企微,补齐 Linux/CI 入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
output
|
||||||
|
.cache
|
||||||
|
logs
|
||||||
|
bot/.env
|
||||||
|
bot/.venv
|
||||||
|
bot/.cache
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
.idea
|
||||||
|
tmp_*
|
||||||
|
tests
|
||||||
|
.pytest_cache
|
||||||
|
*.md
|
||||||
|
!skills/**/*.md
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
# 企微群机器人 webhook(早报推送,与 bot API 模式凭证不同)
|
# 企微群机器人 webhook(早报推送,与 bot API 模式凭证不同)
|
||||||
WECOM_WEBHOOK_KEY=your-webhook-key
|
WECOM_WEBHOOK_KEY=your-webhook-key
|
||||||
|
|
||||||
|
# GitHub Actions:将上述 key 与下方可选项写入 repo Secrets / Variables
|
||||||
|
# 详见 README「部署(GitHub Actions / Docker)」
|
||||||
|
|
||||||
# 早报内容
|
# 早报内容
|
||||||
DAILY_TRENDING_LIMIT=150
|
DAILY_TRENDING_LIMIT=150
|
||||||
DAILY_HOT_LIMIT=150
|
DAILY_HOT_LIMIT=150
|
||||||
|
|||||||
64
.github/workflows/daily.yml
vendored
Normal file
64
.github/workflows/daily.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: daily
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# 08:30 CST (UTC+8) = 00:30 UTC
|
||||||
|
- cron: "30 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
skip_push:
|
||||||
|
description: Skip WeCom push (generate only)
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: daily-report
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
report:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
WECOM_WEBHOOK_KEY: ${{ secrets.WECOM_WEBHOOK_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GH_PAT || github.token }}
|
||||||
|
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
|
||||||
|
DAILY_LLM_API_KEY: ${{ secrets.DAILY_LLM_API_KEY }}
|
||||||
|
DAILY_LLM_API_BASE: ${{ vars.DAILY_LLM_API_BASE }}
|
||||||
|
DAILY_LLM_MODEL: ${{ vars.DAILY_LLM_MODEL }}
|
||||||
|
DAILY_REPORT_MODE: ${{ vars.DAILY_REPORT_MODE }}
|
||||||
|
CURSOR_MODEL: ${{ vars.CURSOR_MODEL }}
|
||||||
|
DAILY_AI_NEWS: ${{ vars.DAILY_AI_NEWS }}
|
||||||
|
DAILY_CN_AI_NEWS: ${{ vars.DAILY_CN_AI_NEWS }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
cache: pip
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Generate and push daily report
|
||||||
|
run: |
|
||||||
|
ARGS=()
|
||||||
|
if [ "${{ inputs.skip_push }}" = "true" ]; then
|
||||||
|
ARGS+=(--skip-push)
|
||||||
|
fi
|
||||||
|
chmod +x ./run-daily.sh
|
||||||
|
./run-daily.sh --force "${ARGS[@]}"
|
||||||
|
|
||||||
|
- name: Upload report artifacts
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: daily-report-${{ github.run_id }}
|
||||||
|
path: |
|
||||||
|
output/*.md
|
||||||
|
output/*.json
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 14
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY daily/ daily/
|
||||||
|
COPY shared/ shared/
|
||||||
|
COPY skills/ skills/
|
||||||
|
COPY bot/ bot/
|
||||||
|
COPY run-daily.sh .
|
||||||
|
|
||||||
|
RUN chmod +x run-daily.sh \
|
||||||
|
&& mkdir -p output .cache logs
|
||||||
|
|
||||||
|
VOLUME ["/app/output", "/app/.cache", "/app/logs"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["./run-daily.sh"]
|
||||||
43
README.md
43
README.md
@@ -36,10 +36,53 @@
|
|||||||
python -m daily push output\2026-07-03.wecom.md # 只推送
|
python -m daily push output\2026-07-03.wecom.md # 只推送
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Linux / macOS / CI 等价脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x run-daily.sh
|
||||||
|
./run-daily.sh # 生成 + 推送
|
||||||
|
./run-daily.sh --skip-push # 只生成
|
||||||
|
./run-daily.sh --force # 忽略 30 分钟内重复运行锁
|
||||||
|
```
|
||||||
|
|
||||||
**前置条件**:Python 3.10+、企业微信群机器人 webhook。无需 Bot API 凭证。
|
**前置条件**:Python 3.10+、企业微信群机器人 webhook。无需 Bot API 凭证。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 部署(GitHub Actions / Docker)
|
||||||
|
|
||||||
|
### GitHub Actions(零服务器定时推送)
|
||||||
|
|
||||||
|
仓库已包含 [`.github/workflows/daily.yml`](.github/workflows/daily.yml),默认每天 **08:30(北京时间)** 生成并推送。
|
||||||
|
|
||||||
|
1. Fork 或启用本仓库的 Actions
|
||||||
|
2. **Settings → Secrets and variables → Actions** 添加 Secrets:
|
||||||
|
|
||||||
|
| Secret | 必需 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `WECOM_WEBHOOK_KEY` | 是 | 企微群机器人 webhook key |
|
||||||
|
| `GH_PAT` | 否 | 个人 GitHub PAT(提高 API 限额;不设则用 Actions 内置 token) |
|
||||||
|
| `CURSOR_API_KEY` | 否 | Agent 模式(Cursor SDK) |
|
||||||
|
| `DAILY_LLM_API_KEY` | 否 | Agent / 编辑层(OpenAI 兼容 API) |
|
||||||
|
|
||||||
|
3. 可选 **Variables**(不设则用代码默认值):`DAILY_REPORT_MODE`、`DAILY_LLM_MODEL`、`DAILY_AI_NEWS` 等
|
||||||
|
4. **Actions → daily → Run workflow** 可手动触发;勾选 *Skip WeCom push* 则只生成不上报
|
||||||
|
|
||||||
|
运行产物可在 workflow 的 **Artifacts** 中下载(`.md` / `.json`,保留 14 天)。
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env # 填入 WECOM_WEBHOOK_KEY 等
|
||||||
|
docker compose build
|
||||||
|
docker compose run --rm daily # 生成 + 推送
|
||||||
|
docker compose run --rm daily --skip-push # 只生成
|
||||||
|
```
|
||||||
|
|
||||||
|
`output/`、`.cache/`、`logs/` 挂载到宿主机,与本地 `run-daily.ps1` 行为一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Agent 模式(推荐)
|
## Agent 模式(推荐)
|
||||||
|
|
||||||
默认 `classic` 是分区榜单模板;**推荐 Agent 模式**——导语 + 今日信号 + 精选新闻 + 榜单,全中文叙述。
|
默认 `classic` 是分区榜单模板;**推荐 Agent 模式**——导语 + 今日信号 + 精选新闻 + 榜单,全中文叙述。
|
||||||
|
|||||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
daily:
|
||||||
|
build: .
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
volumes:
|
||||||
|
- ./output:/app/output
|
||||||
|
- ./.cache:/app/.cache
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# Examples:
|
||||||
|
# docker compose run --rm daily
|
||||||
|
# docker compose run --rm daily --skip-push
|
||||||
|
# docker compose run --rm daily --force
|
||||||
106
run-daily.sh
Normal file
106
run-daily.sh
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Daily report: generate + push to WeCom webhook
|
||||||
|
# Usage:
|
||||||
|
# ./run-daily.sh
|
||||||
|
# ./run-daily.sh --skip-push
|
||||||
|
# ./run-daily.sh --skip-generate
|
||||||
|
# ./run-daily.sh --force
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
LOCK_FILE="$ROOT/.cache/run-daily.lock"
|
||||||
|
LOCK_MAX_MINUTES=30
|
||||||
|
SKIP_PUSH=0
|
||||||
|
SKIP_GENERATE=0
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--skip-push) SKIP_PUSH=1 ;;
|
||||||
|
--skip-generate) SKIP_GENERATE=1 ;;
|
||||||
|
--force) FORCE=1 ;;
|
||||||
|
-h|--help)
|
||||||
|
echo "Usage: $0 [--skip-push] [--skip-generate] [--force]"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
load_dotenv() {
|
||||||
|
local file="$1"
|
||||||
|
[[ -f "$file" ]] || return 0
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
[[ "$line" != *"="* ]] && continue
|
||||||
|
local name="${line%%=*}"
|
||||||
|
local value="${line#*=}"
|
||||||
|
name="${name#"${name%%[![:space:]]*}"}"
|
||||||
|
name="${name%"${name##*[![:space:]]}"}"
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
value="${value%\"}"
|
||||||
|
value="${value#\"}"
|
||||||
|
value="${value%\'}"
|
||||||
|
value="${value#\'}"
|
||||||
|
if [[ -n "$name" && -n "$value" ]]; then
|
||||||
|
export "$name=$value"
|
||||||
|
fi
|
||||||
|
done < "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_active() {
|
||||||
|
[[ -f "$LOCK_FILE" ]] || return 1
|
||||||
|
local now lock_ts age_minutes
|
||||||
|
now="$(date +%s)"
|
||||||
|
lock_ts="$(date -r "$LOCK_FILE" +%s 2>/dev/null || stat -c %Y "$LOCK_FILE" 2>/dev/null || echo 0)"
|
||||||
|
age_minutes=$(( (now - lock_ts) / 60 ))
|
||||||
|
[[ "$age_minutes" -lt "$LOCK_MAX_MINUTES" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
set_lock() {
|
||||||
|
mkdir -p "$(dirname "$LOCK_FILE")"
|
||||||
|
date -Iseconds > "$LOCK_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_lock() {
|
||||||
|
rm -f "$LOCK_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$FORCE" -eq 0 ]] && lock_active; then
|
||||||
|
echo "Skip: run-daily already ran within ${LOCK_MAX_MINUTES} minutes (lock: $LOCK_FILE). Use --force to override."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
set_lock
|
||||||
|
trap clear_lock EXIT
|
||||||
|
|
||||||
|
load_dotenv "$ROOT/.env"
|
||||||
|
load_dotenv "$ROOT/.env.local"
|
||||||
|
|
||||||
|
PYTHON="${PYTHON:-python}"
|
||||||
|
DATE="$(TZ=Asia/Shanghai date +%Y-%m-%d)"
|
||||||
|
REPORT_WECOM="$ROOT/output/${DATE}.wecom.md"
|
||||||
|
|
||||||
|
if [[ "$SKIP_GENERATE" -eq 0 ]]; then
|
||||||
|
echo "Generating daily report: $DATE"
|
||||||
|
"$PYTHON" -m daily generate
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SKIP_PUSH" -eq 0 ]]; then
|
||||||
|
if [[ ! -f "$REPORT_WECOM" ]]; then
|
||||||
|
echo "Report not found: $REPORT_WECOM" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Pushing to WeCom webhook..."
|
||||||
|
"$PYTHON" -m daily push "$REPORT_WECOM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done: $DATE"
|
||||||
Reference in New Issue
Block a user