Commit 216de72b authored by 谢宇轩's avatar 谢宇轩

fix: some path bug

parent ab0f8df6
......@@ -2,7 +2,7 @@
# add-source.sh — 新增一个抓取栏目(timer + 可选 conf),自动错开时间
#
# 用法:
# sudo ./add-source.sh <source-name> [选项]
# sudo ./bin/add-source.sh <source-name> [选项]
#
# 选项:
# --time HH:MM 运行时间,如 09:30(不指定则自动错开:现有最晚时间 +15 分钟)
......@@ -16,8 +16,8 @@
# --help, -h 显示帮助
#
# 示例:
# sudo ./add-source.sh bloomberg --time 09:30 --count 30 --mail-on-success 0
# sudo ./add-source.sh nikkei-asia # 自动错开到下一个空档
# sudo ./bin/add-source.sh bloomberg --time 09:30 --count 30 --mail-on-success 0
# sudo ./bin/add-source.sh nikkei-asia # 自动错开到下一个空档
#
# 环境变量(可覆盖默认路径):
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
......
......@@ -2,12 +2,12 @@
# deploy.sh — 一键同步项目到服务器
#
# 用法:
# ./deploy.sh <server-ip> # 默认用户 yichu
# ./deploy.sh yichu@192.168.1.100 # 显式指定用户
# ./deploy.sh <host> --no-systemd # 只更新 bin 脚本,不动 systemd unit
# ./deploy.sh <host> --no-restart-web # 不重启 web 面板
# ./deploy.sh <host> --no-providers # 不同步 provider 配置
# ./deploy.sh <host> --test # 部署后自动跑一次 nikkei-world 测试
# ./bin/deploy.sh <server-ip> # 默认用户 yichu
# ./bin/deploy.sh yichu@192.168.1.100 # 显式指定用户
# ./bin/deploy.sh <host> --no-systemd # 只更新 bin 脚本,不动 systemd unit
# ./bin/deploy.sh <host> --no-restart-web # 不重启 web 面板
# ./bin/deploy.sh <host> --no-providers # 不同步 provider 配置
# ./bin/deploy.sh <host> --test # 部署后自动跑一次 nikkei-world 测试
#
# 做的事:
# 1. rsync 整个项目目录到 ~/news-harvester-pro/(--delete 保持镜像)
......@@ -63,6 +63,7 @@ info() { echo "ℹ️ $*"; }
warn() { echo "⚠️ $*"; }
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_NAME="news-harvester-pro"
# --- 检查 rsync ---
......@@ -85,12 +86,13 @@ rsync -avz --delete \
--exclude='logs/' \
--exclude='__pycache__/' \
--exclude='*.pyc' \
"$SCRIPT_DIR/" "$HOST:~/${PROJECT_NAME}/"
"$REPO_DIR/" "$HOST:~/${PROJECT_NAME}/"
ok "项目目录已同步"
# --- 2. 复制 bin + web 到工作目录 ---
info "2/6 复制 bin/*.sh + web/ → ~/Documents/News-Harvester/"
ssh "$HOST" 'mkdir -p ~/Documents/News-Harvester/bin ~/Documents/News-Harvester/web/templates && cp ~/news-harvester-pro/bin/*.sh ~/Documents/News-Harvester/bin/ && chmod +x ~/Documents/News-Harvester/bin/*.sh && cp ~/news-harvester-pro/web/app.py ~/Documents/News-Harvester/web/ && cp ~/news-harvester-pro/web/templates/*.html ~/Documents/News-Harvester/web/templates/'
# deploy.sh / migrate.sh 是仓库级工具,不复制到运行目录
ssh "$HOST" 'mkdir -p ~/Documents/News-Harvester/bin ~/Documents/News-Harvester/web/templates && for f in ~/news-harvester-pro/bin/*.sh; do case "$(basename "$f")" in deploy.sh|migrate.sh) ;; *) cp "$f" ~/Documents/News-Harvester/bin/ ;; esac; done && chmod +x ~/Documents/News-Harvester/bin/*.sh && cp ~/news-harvester-pro/web/app.py ~/Documents/News-Harvester/web/ && cp ~/news-harvester-pro/web/templates/*.html ~/Documents/News-Harvester/web/templates/'
ok "bin 脚本 + web 应用已就位"
# --- 3. 同步 systemd unit ---
......@@ -109,8 +111,8 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
info "4/6 同步 provider 配置 → /etc/news-harvester/providers/"
# 检查本地是否有 provider JSON(不含 .example)
local_providers=""
if [ -d "$SCRIPT_DIR/config/providers" ]; then
local_providers=$(ls "$SCRIPT_DIR/config/providers/"*.json 2>/dev/null | grep -v '\.example\.json$' || true)
if [ -d "$REPO_DIR/config/providers" ]; then
local_providers=$(ls "$REPO_DIR/config/providers/"*.json 2>/dev/null | grep -v '\.example\.json$' || true)
fi
if [ -z "$local_providers" ]; then
......@@ -126,7 +128,7 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
--include='*.json' \
--exclude='*.example.json' \
--exclude='*' \
"$SCRIPT_DIR/config/providers/" "$HOST:~/news-harvester-pro/.providers-tmp/"
"$REPO_DIR/config/providers/" "$HOST:~/news-harvester-pro/.providers-tmp/"
# 远程:创建目录(如不存在),复制 JSON,设置权限,清理临时目录
ssh -t "$HOST" 'sudo mkdir -p /etc/news-harvester/providers && sudo cp ~/news-harvester-pro/.providers-tmp/*.json /etc/news-harvester/providers/ 2>/dev/null && sudo chmod 644 /etc/news-harvester/providers/*.json && rm -rf ~/news-harvester-pro/.providers-tmp' 2>/dev/null \
......
......@@ -10,9 +10,9 @@
# 6. 可选:安装 digest service + timer
#
# 用法:
# sudo ./migrate.sh # 交互式(会提示确认)
# sudo ./migrate.sh --yes # 跳过所有确认
# sudo ./migrate.sh --uninstall # 卸载新版,恢复旧版备份
# sudo ./bin/migrate.sh # 交互式(会提示确认)
# sudo ./bin/migrate.sh --yes # 跳过所有确认
# sudo ./bin/migrate.sh --uninstall # 卸载新版,恢复旧版备份
#
# 环境变量:
# WORK_DIR 项目工作目录(默认 /home/yichu/Documents/News-Harvester)
......@@ -26,6 +26,7 @@ WORK_DIR="${WORK_DIR:-/home/yichu/Documents/News-Harvester}"
USER_NAME="${USER_NAME:-yichu}"
INSTALL_DIGEST="${INSTALL_DIGEST:-1}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
BACKUP_DIR="${WORK_DIR}/.backup-$(date +%Y%m%d%H%M%S)"
ASSUME_NO=0
UNINSTALL=0
......@@ -165,11 +166,14 @@ fi
echo ""
echo "=== 步骤 3/5: 安装新版脚本 ==="
mkdir -p "$WORK_DIR/bin"
install -m755 -o "$USER_NAME" -g "$USER_NAME" "$SCRIPT_DIR/bin/harvest.sh" "$WORK_DIR/bin/harvest.sh"
install -m755 -o "$USER_NAME" -g "$USER_NAME" "$SCRIPT_DIR/bin/notify-mail.sh" "$WORK_DIR/bin/notify-mail.sh"
if [ "$INSTALL_DIGEST" = "1" ]; then
install -m755 -o "$USER_NAME" -g "$USER_NAME" "$SCRIPT_DIR/bin/daily-digest.sh" "$WORK_DIR/bin/daily-digest.sh"
fi
for f in "$SCRIPT_DIR"/*.sh; do
base="$(basename "$f")"
# deploy.sh / migrate.sh 是仓库级工具,不装到运行目录
case "$base" in deploy.sh|migrate.sh) continue ;; esac
# digest 相关脚本按开关安装
[ "$base" = "daily-digest.sh" ] && [ "$INSTALL_DIGEST" != "1" ] && continue
install -m755 -o "$USER_NAME" -g "$USER_NAME" "$f" "$WORK_DIR/bin/$base"
done
ok "脚本已安装到 $WORK_DIR/bin/"
# --- 4. 安装 systemd unit ---
......@@ -179,7 +183,7 @@ echo "=== 步骤 4/5: 安装 systemd 单元 ==="
rm -f /etc/systemd/system/news-harvester@.service
rm -f /etc/systemd/system/news-harvester-digest.service /etc/systemd/system/news-harvester-digest.timer
install -m644 "$SCRIPT_DIR/systemd/news-harvester@.service" /etc/systemd/system/
install -m644 "$REPO_DIR/systemd/news-harvester@.service" /etc/systemd/system/
# 安装栏目 timer:优先用备份里用户改过的版本,否则用项目自带示例
# 收集所有旧 timer 的栏目名
......@@ -195,7 +199,7 @@ done
# 如果备份里没有(全新安装),装示例 timer
if [ "${#INSTALLED_TIMERS[@]}" -eq 0 ]; then
for example_timer in "$SCRIPT_DIR/systemd"/news-harvester@*.timer; do
for example_timer in "$REPO_DIR/systemd"/news-harvester@*.timer; do
[ -f "$example_timer" ] || continue
timer_name="$(basename "$example_timer")"
install -m644 "$example_timer" "/etc/systemd/system/$timer_name"
......@@ -205,8 +209,8 @@ if [ "${#INSTALLED_TIMERS[@]}" -eq 0 ]; then
fi
if [ "$INSTALL_DIGEST" = "1" ]; then
install -m644 "$SCRIPT_DIR/systemd/news-harvester-digest.service" /etc/systemd/system/
install -m644 "$SCRIPT_DIR/systemd/news-harvester-digest.timer" /etc/systemd/system/
install -m644 "$REPO_DIR/systemd/news-harvester-digest.service" /etc/systemd/system/
install -m644 "$REPO_DIR/systemd/news-harvester-digest.timer" /etc/systemd/system/
ok "digest service + timer 已安装"
fi
......@@ -218,7 +222,7 @@ echo ""
echo "=== 步骤 5/5: 配置文件 ==="
install -d -m755 /etc/news-harvester
if [ ! -f /etc/news-harvester/default.conf ]; then
install -m644 "$SCRIPT_DIR/config/default.conf.example" /etc/news-harvester/default.conf
install -m644 "$REPO_DIR/config/default.conf.example" /etc/news-harvester/default.conf
warn "已创建 /etc/news-harvester/default.conf(示例),请编辑填入真实 MAIL_TO"
else
ok "/etc/news-harvester/default.conf 已存在,保留不覆盖"
......@@ -247,7 +251,7 @@ echo "========================================"
echo ""
echo "后续操作:"
echo " 1. 编辑配置: sudo vim /etc/news-harvester/default.conf"
echo " 2. 配置 SMTP: install -m600 config/msmtprc.example ~${USER_NAME}/.msmtprc && vim ~${USER_NAME}/.msmtprc"
echo " 2. 配置 SMTP: install -m600 $REPO_DIR/config/msmtprc.example ~${USER_NAME}/.msmtprc && vim ~${USER_NAME}/.msmtprc"
echo " 3. 查看定时器: systemctl list-timers 'news-harvester@*' news-harvester-digest.timer"
echo " 4. 手动测试: sudo systemctl start news-harvester@nikkei-world.service"
echo " 5. 查看日志: journalctl -u 'news-harvester@*' --since today -f"
......
......@@ -2,7 +2,7 @@
# modify-source.sh — 修改已有栏目的定时运行时间(及可选配置)
#
# 用法:
# sudo ./modify-source.sh <source-name> [选项]
# sudo ./bin/modify-source.sh <source-name> [选项]
#
# 选项:
# --time HH:MM 新的运行时间,如 10:30(必填)
......@@ -15,8 +15,8 @@
# --help, -h 显示帮助
#
# 示例:
# sudo ./modify-source.sh bloomberg --time 10:30
# sudo ./modify-source.sh nikkei-asia --time 14:00 --count 20 --mail-on-success 0
# sudo ./bin/modify-source.sh bloomberg --time 10:30
# sudo ./bin/modify-source.sh nikkei-asia --time 14:00 --count 20 --mail-on-success 0
#
# 环境变量(可覆盖默认路径):
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
......@@ -102,15 +102,17 @@ DROPIN_DIR="$SYSTEMD_DIR/${TIMER_NAME}.d"
# --- 读取当前生效时间(优先 systemctl show,回退读文件) ---
CURRENT_TIME=""
# 方法1:从 systemctl show 读实际生效值(能反映 drop-in 覆盖)
local_show=$(systemctl show "$TIMER_NAME" -p OnCalendar 2>/dev/null || true)
if [ -n "$local_show" ] && [ "$local_show" != "OnCalendar=" ]; then
CURRENT_TIME=$(echo "$local_show" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1 || true)
# 先读直接属性 OnCalendar,为空再从 TimersCalendar={ OnCalendar=... } 中匹配
local_show=$(systemctl show "$TIMER_NAME" -p OnCalendar -p TimersCalendar 2>/dev/null || true)
local_cal=$(echo "$local_show" | grep '^OnCalendar=' | cut -d= -f2- | tr '\n' ' ' || true)
if [ -z "${local_cal// /}" ]; then
local_cal=$(echo "$local_show" | grep -oE 'OnCalendar=[^;}]*' | cut -d= -f2- | tr '\n' ' ' || true)
fi
CURRENT_TIME=$(echo "$local_cal" | grep -oE '[0-9]{2}:[0-9]{2}' | head -1 || true)
# 方法2:回退到直接读 timer 文件
if [ -z "$CURRENT_TIME" ]; then
CURRENT_TIME=$(grep '^OnCalendar=' "$TIMER_PATH" 2>/dev/null | head -1 | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1 || true)
CURRENT_TIME=$(grep '^OnCalendar=' "$TIMER_PATH" 2>/dev/null | head -1 | grep -oE '[0-9]{2}:[0-9]{2}' | head -1 || true)
fi
[ -n "$CURRENT_TIME" ] && CURRENT_TIME="${CURRENT_TIME%:*}" # 取 HH:MM
[ -z "$CURRENT_TIME" ] && CURRENT_TIME="(未知)"
# --- 检查是否有 drop-in 覆盖(若有,改主文件不生效,需要先清除) ---
......
......@@ -35,6 +35,19 @@ EOF
hr() { echo "------------------------------------------------------------"; }
# 读取 timer 实际生效的 OnCalendar(含 drop-in 覆盖):
# 先读直接属性 OnCalendar,为空再从 TimersCalendar={ OnCalendar=... ; ... } 中匹配
# 可能返回多个值(drop-in 追加时),空格拼接;读不到输出空
get_oncalendar() {
local out val
out=$(systemctl show "$1" -p OnCalendar -p TimersCalendar 2>/dev/null) || true
val=$(echo "$out" | grep '^OnCalendar=' | cut -d= -f2- | tr '\n' ' ')
if [ -z "${val// /}" ]; then
val=$(echo "$out" | grep -oE 'OnCalendar=[^;}]*' | cut -d= -f2- | tr '\n' ' ')
fi
printf '%s' "$val"
}
# ============================================================
# 所有栏目的最近日志摘要
# ============================================================
......@@ -110,7 +123,7 @@ show_overview() {
if [ -z "$timer_names" ]; then
echo "未发现任何 news-harvester timer。"
echo "提示:用 sudo ./add-source.sh <source> 添加栏目"
echo "提示:用 sudo ~/news-harvester-pro/bin/add-source.sh <source> 添加栏目"
return
fi
......@@ -130,26 +143,25 @@ show_overview() {
sub=$(systemctl is-active "$name" 2>/dev/null || echo "unknown")
# 获取 OnCalendar / NextElapse / LastTrigger
# OnCalendar 可能返回多行(drop-in 覆盖时),用 grep 取所有值
local on_cal next_el last_tr
on_cal=$(systemctl show "$name" -p OnCalendar 2>/dev/null | grep '^OnCalendar=' | cut -d= -f2- | tr '\n' ' ')
on_cal=$(get_oncalendar "$name")
next_el=$(systemctl show "$name" -p NextElapseUSecRealtime 2>/dev/null | cut -d= -f2-)
last_tr=$(systemctl show "$name" -p LastTriggerUSec 2>/dev/null | cut -d= -f2-)
# 提取 HH:MM 排序键(从 OnCalendar 或 NextElapse 兜底)
# OnCalendar 格式: *-*-* 09:00:00
# OnCalendar 格式: *-*-* 09:00:00 或 *-*-* 09:00(秒可省略)
# NextElapse 格式: Wed 2026-08-27 09:00:00 CST
local short sort_key run_fmt
# 先从 OnCalendar 提取
short=$(echo "$on_cal" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1)
# 先从 OnCalendar 提取(直接取 HH:MM,兼容带秒/不带秒两种写法)
short=$(echo "$on_cal" | grep -oE '[0-9]{2}:[0-9]{2}' | head -1)
# 如果 OnCalendar 没有,从 NextElapse 提取(去掉星期和日期,只留时间)
if [ -z "$short" ] && [ -n "$next_el" ] && [ "$next_el" != "n/a" ]; then
short=$(echo "$next_el" | grep -oE '[0-9]{2}:[0-9]{2}:[0-9]{2}' | head -1)
short=$(echo "$next_el" | grep -oE '[0-9]{2}:[0-9]{2}' | head -1)
fi
if [ -n "$short" ]; then
sort_key="${short%:*}" # HH:MM
run_fmt="${short%:*}" # HH:MM
sort_key="$short"
run_fmt="$short"
else
sort_key="99:99"
run_fmt="-"
......@@ -207,7 +219,7 @@ show_source_detail() {
if systemctl cat "$timer_name" >/dev/null 2>&1; then
echo "Timer:"
local on_cal next_el last_tr
on_cal=$(systemctl show "$timer_name" -p OnCalendar 2>/dev/null | cut -d= -f2)
on_cal=$(get_oncalendar "$timer_name")
next_el=$(systemctl show "$timer_name" -p NextElapseUSecRealtime 2>/dev/null | cut -d= -f2-)
last_tr=$(systemctl show "$timer_name" -p LastTriggerUSec 2>/dev/null | cut -d= -f2-)
......
......@@ -149,25 +149,36 @@ def get_timer_files():
def parse_timer_file(timer_path):
"""从 timer 文件解析 OnCalendar 时间"""
text = timer_path.read_text(encoding="utf-8", errors="replace")
m = re.search(r"^OnCalendar=.*?\d{2}:\d{2}:\d{2}", text, re.MULTILINE)
try:
text = timer_path.read_text(encoding="utf-8", errors="replace")
except OSError:
return "-"
m = re.search(r"^OnCalendar=.*?\d{2}:\d{2}(?::\d{2})?", text, re.MULTILINE)
if m:
time_match = re.search(r"(\d{2}):(\d{2}):\d{2}", m.group(0))
time_match = re.search(r"(\d{2}):(\d{2})(?::\d{2})?", m.group(0))
if time_match:
return f"{time_match.group(1)}:{time_match.group(2)}"
return "-"
def read_oncalendar_from_systemd(timer_name):
"""从 systemctl show 读取实际生效的 OnCalendar(含 drop-in 覆盖)"""
"""从 systemctl show 读取实际生效的 OnCalendar(含 drop-in 覆盖)。
先读直接属性 OnCalendar;多数版本该属性输出为空,再从
TimersCalendar={ OnCalendar=*-*-* 22:00:00 ; next_elapse=... CST } 中提取。
秒可以省略(OnCalendar=*-*-* 22:00 合法),多个表达式时取最后一个。"""
try:
out = subprocess.run(
["systemctl", "show", timer_name, "-p", "OnCalendar"],
["systemctl", "show", timer_name, "-p", "OnCalendar", "-p", "TimersCalendar"],
capture_output=True, text=True, timeout=5,
).stdout
m = re.search(r"(\d{2}):(\d{2}):\d{2}", out)
if m:
return f"{m.group(1)}:{m.group(2)}"
specs = [s for s in re.findall(r"^OnCalendar=(.*)$", out, re.MULTILINE) if s.strip()]
if not specs:
specs = re.findall(r"OnCalendar=([^;}\n]*)", out)
for spec in reversed(specs):
m = re.search(r"(\d{2}):(\d{2})(?::\d{2})?", spec)
if m:
return f"{m.group(1)}:{m.group(2)}"
except Exception:
pass
return None
......@@ -379,7 +390,18 @@ def index():
sources = get_source_list()
# digest timer 状态
digest_props = get_systemd_props("news-harvester-digest.timer")
digest_run_time = read_oncalendar_from_systemd("news-harvester-digest.timer") or "10:00"
digest_run_time = (read_oncalendar_from_systemd("news-harvester-digest.timer")
or parse_timer_file(SYSTEMD_DIR / "news-harvester-digest.timer"))
# 全局默认 provider(default.conf;systemd 先加载它再加载栏目 conf,栏目未设置时继承)
# 值为 "default" 等同未设置,使用系统全局配置
default_conf = read_conf("default")
default_provider = default_conf.get("PROVIDER", "").strip()
if default_provider == "default":
default_provider = ""
default_fallback = default_conf.get("FALLBACK_PROVIDER", "").strip()
if default_fallback == "default":
default_fallback = ""
# 后台 digest 任务状态
digest_status = _check_digest_status()
......@@ -395,7 +417,9 @@ def index():
return render_template("index.html", sources=sources,
digest_props=digest_props, digest_run_time=digest_run_time,
digest_status=digest_status, auto_refresh=auto_refresh)
digest_status=digest_status, auto_refresh=auto_refresh,
default_provider=default_provider,
default_fallback=default_fallback)
@app.route("/source/<name>")
......
......@@ -61,10 +61,12 @@
{% if s.provider %}
<span class="badge ok">{{ s.provider }}</span>
{% else %}
<span style="color:#666">默认</span>
<span style="color:#666" title="栏目未单独设置,继承 default.conf 全局默认{% if default_provider %}: {{ default_provider }}{% endif %}">默认{% if default_provider %}({{ default_provider }}){% endif %}</span>
{% endif %}
{% if s.fallback_provider %}
<span style="font-size:11px;color:#888" title="备用 provider">→ {{ s.fallback_provider }}</span>
{% elif default_fallback %}
<span style="font-size:11px;color:#888" title="备用 provider(继承 default.conf 全局默认)">→ {{ default_fallback }}</span>
{% endif %}
</td>
<td>{{ s.last }}</td>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment