Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
M
macos-runtime
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
郭阔
macos-runtime
Commits
216de72b
Commit
216de72b
authored
Sep 07, 2026
by
谢宇轩
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: some path bug
parent
ab0f8df6
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
103 additions
and
57 deletions
+103
-57
add-source.sh
bin/add-source.sh
+3
-3
deploy.sh
bin/deploy.sh
+13
-11
migrate.sh
bin/migrate.sh
+18
-14
modify-source.sh
bin/modify-source.sh
+10
-8
status.sh
bin/status.sh
+22
-10
app.py
web/app.py
+34
-10
index.html
web/templates/index.html
+3
-1
No files found.
add-source.sh
→
bin/
add-source.sh
View file @
216de72b
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
# add-source.sh — 新增一个抓取栏目(timer + 可选 conf),自动错开时间
# add-source.sh — 新增一个抓取栏目(timer + 可选 conf),自动错开时间
#
#
# 用法:
# 用法:
# sudo ./add-source.sh <source-name> [选项]
# sudo ./
bin/
add-source.sh <source-name> [选项]
#
#
# 选项:
# 选项:
# --time HH:MM 运行时间,如 09:30(不指定则自动错开:现有最晚时间 +15 分钟)
# --time HH:MM 运行时间,如 09:30(不指定则自动错开:现有最晚时间 +15 分钟)
...
@@ -16,8 +16,8 @@
...
@@ -16,8 +16,8 @@
# --help, -h 显示帮助
# --help, -h 显示帮助
#
#
# 示例:
# 示例:
# sudo ./add-source.sh bloomberg --time 09:30 --count 30 --mail-on-success 0
# sudo ./
bin/
add-source.sh bloomberg --time 09:30 --count 30 --mail-on-success 0
# sudo ./add-source.sh nikkei-asia # 自动错开到下一个空档
# sudo ./
bin/
add-source.sh nikkei-asia # 自动错开到下一个空档
#
#
# 环境变量(可覆盖默认路径):
# 环境变量(可覆盖默认路径):
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
...
...
deploy.sh
→
bin/
deploy.sh
View file @
216de72b
...
@@ -2,12 +2,12 @@
...
@@ -2,12 +2,12 @@
# deploy.sh — 一键同步项目到服务器
# deploy.sh — 一键同步项目到服务器
#
#
# 用法:
# 用法:
# ./deploy.sh <server-ip> # 默认用户 yichu
# ./
bin/
deploy.sh <server-ip> # 默认用户 yichu
# ./deploy.sh yichu@192.168.1.100 # 显式指定用户
# ./
bin/
deploy.sh yichu@192.168.1.100 # 显式指定用户
# ./deploy.sh <host> --no-systemd # 只更新 bin 脚本,不动 systemd unit
# ./
bin/
deploy.sh <host> --no-systemd # 只更新 bin 脚本,不动 systemd unit
# ./deploy.sh <host> --no-restart-web # 不重启 web 面板
# ./
bin/
deploy.sh <host> --no-restart-web # 不重启 web 面板
# ./deploy.sh <host> --no-providers # 不同步 provider 配置
# ./
bin/
deploy.sh <host> --no-providers # 不同步 provider 配置
# ./deploy.sh <host> --test # 部署后自动跑一次 nikkei-world 测试
# ./
bin/
deploy.sh <host> --test # 部署后自动跑一次 nikkei-world 测试
#
#
# 做的事:
# 做的事:
# 1. rsync 整个项目目录到 ~/news-harvester-pro/(--delete 保持镜像)
# 1. rsync 整个项目目录到 ~/news-harvester-pro/(--delete 保持镜像)
...
@@ -63,6 +63,7 @@ info() { echo "ℹ️ $*"; }
...
@@ -63,6 +63,7 @@ info() { echo "ℹ️ $*"; }
warn
()
{
echo
"⚠️
$*
"
;
}
warn
()
{
echo
"⚠️
$*
"
;
}
SCRIPT_DIR
=
"
$(
cd
"
$(
dirname
"
$0
"
)
"
&&
pwd
)
"
SCRIPT_DIR
=
"
$(
cd
"
$(
dirname
"
$0
"
)
"
&&
pwd
)
"
REPO_DIR
=
"
$(
cd
"
$SCRIPT_DIR
/.."
&&
pwd
)
"
PROJECT_NAME
=
"news-harvester-pro"
PROJECT_NAME
=
"news-harvester-pro"
# --- 检查 rsync ---
# --- 检查 rsync ---
...
@@ -85,12 +86,13 @@ rsync -avz --delete \
...
@@ -85,12 +86,13 @@ rsync -avz --delete \
--exclude
=
'logs/'
\
--exclude
=
'logs/'
\
--exclude
=
'__pycache__/'
\
--exclude
=
'__pycache__/'
\
--exclude
=
'*.pyc'
\
--exclude
=
'*.pyc'
\
"
$
SCRIPT
_DIR
/"
"
$HOST
:~/
${
PROJECT_NAME
}
/"
"
$
REPO
_DIR
/"
"
$HOST
:~/
${
PROJECT_NAME
}
/"
ok
"项目目录已同步"
ok
"项目目录已同步"
# --- 2. 复制 bin + web 到工作目录 ---
# --- 2. 复制 bin + web 到工作目录 ---
info
"2/6 复制 bin/*.sh + web/ → ~/Documents/News-Harvester/"
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 应用已就位"
ok
"bin 脚本 + web 应用已就位"
# --- 3. 同步 systemd unit ---
# --- 3. 同步 systemd unit ---
...
@@ -109,8 +111,8 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
...
@@ -109,8 +111,8 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
info
"4/6 同步 provider 配置 → /etc/news-harvester/providers/"
info
"4/6 同步 provider 配置 → /etc/news-harvester/providers/"
# 检查本地是否有 provider JSON(不含 .example)
# 检查本地是否有 provider JSON(不含 .example)
local_providers
=
""
local_providers
=
""
if
[
-d
"
$
SCRIPT
_DIR
/config/providers"
]
;
then
if
[
-d
"
$
REPO
_DIR
/config/providers"
]
;
then
local_providers
=
$(
ls
"
$
SCRIPT
_DIR
/config/providers/"
*
.json 2>/dev/null |
grep
-v
'\.example\.json$'
||
true
)
local_providers
=
$(
ls
"
$
REPO
_DIR
/config/providers/"
*
.json 2>/dev/null |
grep
-v
'\.example\.json$'
||
true
)
fi
fi
if
[
-z
"
$local_providers
"
]
;
then
if
[
-z
"
$local_providers
"
]
;
then
...
@@ -126,7 +128,7 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
...
@@ -126,7 +128,7 @@ if [ "$SYNC_PROVIDERS" = "1" ]; then
--include
=
'*.json'
\
--include
=
'*.json'
\
--exclude
=
'*.example.json'
\
--exclude
=
'*.example.json'
\
--exclude
=
'*'
\
--exclude
=
'*'
\
"
$
SCRIPT
_DIR
/config/providers/"
"
$HOST
:~/news-harvester-pro/.providers-tmp/"
"
$
REPO
_DIR
/config/providers/"
"
$HOST
:~/news-harvester-pro/.providers-tmp/"
# 远程:创建目录(如不存在),复制 JSON,设置权限,清理临时目录
# 远程:创建目录(如不存在),复制 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
\
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
\
...
...
migrate.sh
→
bin/
migrate.sh
View file @
216de72b
...
@@ -10,9 +10,9 @@
...
@@ -10,9 +10,9 @@
# 6. 可选:安装 digest service + timer
# 6. 可选:安装 digest service + timer
#
#
# 用法:
# 用法:
# sudo ./migrate.sh # 交互式(会提示确认)
# sudo ./
bin/
migrate.sh # 交互式(会提示确认)
# sudo ./migrate.sh --yes # 跳过所有确认
# sudo ./
bin/
migrate.sh --yes # 跳过所有确认
# sudo ./migrate.sh --uninstall # 卸载新版,恢复旧版备份
# sudo ./
bin/
migrate.sh --uninstall # 卸载新版,恢复旧版备份
#
#
# 环境变量:
# 环境变量:
# WORK_DIR 项目工作目录(默认 /home/yichu/Documents/News-Harvester)
# WORK_DIR 项目工作目录(默认 /home/yichu/Documents/News-Harvester)
...
@@ -26,6 +26,7 @@ WORK_DIR="${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
}
"
USER_NAME
=
"
${
USER_NAME
:-
yichu
}
"
INSTALL_DIGEST
=
"
${
INSTALL_DIGEST
:-
1
}
"
INSTALL_DIGEST
=
"
${
INSTALL_DIGEST
:-
1
}
"
SCRIPT_DIR
=
"
$(
cd
"
$(
dirname
"
$0
"
)
"
&&
pwd
)
"
SCRIPT_DIR
=
"
$(
cd
"
$(
dirname
"
$0
"
)
"
&&
pwd
)
"
REPO_DIR
=
"
$(
cd
"
$SCRIPT_DIR
/.."
&&
pwd
)
"
BACKUP_DIR
=
"
${
WORK_DIR
}
/.backup-
$(
date
+%Y%m%d%H%M%S
)
"
BACKUP_DIR
=
"
${
WORK_DIR
}
/.backup-
$(
date
+%Y%m%d%H%M%S
)
"
ASSUME_NO
=
0
ASSUME_NO
=
0
UNINSTALL
=
0
UNINSTALL
=
0
...
@@ -165,11 +166,14 @@ fi
...
@@ -165,11 +166,14 @@ fi
echo
""
echo
""
echo
"=== 步骤 3/5: 安装新版脚本 ==="
echo
"=== 步骤 3/5: 安装新版脚本 ==="
mkdir
-p
"
$WORK_DIR
/bin"
mkdir
-p
"
$WORK_DIR
/bin"
install
-m755
-o
"
$USER_NAME
"
-g
"
$USER_NAME
"
"
$SCRIPT_DIR
/bin/harvest.sh"
"
$WORK_DIR
/bin/harvest.sh"
for
f
in
"
$SCRIPT_DIR
"
/
*
.sh
;
do
install
-m755
-o
"
$USER_NAME
"
-g
"
$USER_NAME
"
"
$SCRIPT_DIR
/bin/notify-mail.sh"
"
$WORK_DIR
/bin/notify-mail.sh"
base
=
"
$(
basename
"
$f
"
)
"
if
[
"
$INSTALL_DIGEST
"
=
"1"
]
;
then
# deploy.sh / migrate.sh 是仓库级工具,不装到运行目录
install
-m755
-o
"
$USER_NAME
"
-g
"
$USER_NAME
"
"
$SCRIPT_DIR
/bin/daily-digest.sh"
"
$WORK_DIR
/bin/daily-digest.sh"
case
"
$base
"
in
deploy.sh|migrate.sh
)
continue
;;
esac
fi
# 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/"
ok
"脚本已安装到
$WORK_DIR
/bin/"
# --- 4. 安装 systemd unit ---
# --- 4. 安装 systemd unit ---
...
@@ -179,7 +183,7 @@ echo "=== 步骤 4/5: 安装 systemd 单元 ==="
...
@@ -179,7 +183,7 @@ echo "=== 步骤 4/5: 安装 systemd 单元 ==="
rm
-f
/etc/systemd/system/news-harvester@.service
rm
-f
/etc/systemd/system/news-harvester@.service
rm
-f
/etc/systemd/system/news-harvester-digest.service /etc/systemd/system/news-harvester-digest.timer
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:优先用备份里用户改过的版本,否则用项目自带示例
# 收集所有旧 timer 的栏目名
# 收集所有旧 timer 的栏目名
...
@@ -195,7 +199,7 @@ done
...
@@ -195,7 +199,7 @@ done
# 如果备份里没有(全新安装),装示例 timer
# 如果备份里没有(全新安装),装示例 timer
if
[
"
${#
INSTALLED_TIMERS
[@]
}
"
-eq
0
]
;
then
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
[
-f
"
$example_timer
"
]
||
continue
timer_name
=
"
$(
basename
"
$example_timer
"
)
"
timer_name
=
"
$(
basename
"
$example_timer
"
)
"
install
-m644
"
$example_timer
"
"/etc/systemd/system/
$timer_name
"
install
-m644
"
$example_timer
"
"/etc/systemd/system/
$timer_name
"
...
@@ -205,8 +209,8 @@ if [ "${#INSTALLED_TIMERS[@]}" -eq 0 ]; then
...
@@ -205,8 +209,8 @@ if [ "${#INSTALLED_TIMERS[@]}" -eq 0 ]; then
fi
fi
if
[
"
$INSTALL_DIGEST
"
=
"1"
]
;
then
if
[
"
$INSTALL_DIGEST
"
=
"1"
]
;
then
install
-m644
"
$
SCRIPT
_DIR
/systemd/news-harvester-digest.service"
/etc/systemd/system/
install
-m644
"
$
REPO
_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.timer"
/etc/systemd/system/
ok
"digest service + timer 已安装"
ok
"digest service + timer 已安装"
fi
fi
...
@@ -218,7 +222,7 @@ echo ""
...
@@ -218,7 +222,7 @@ echo ""
echo
"=== 步骤 5/5: 配置文件 ==="
echo
"=== 步骤 5/5: 配置文件 ==="
install
-d
-m755
/etc/news-harvester
install
-d
-m755
/etc/news-harvester
if
[
!
-f
/etc/news-harvester/default.conf
]
;
then
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"
warn
"已创建 /etc/news-harvester/default.conf(示例),请编辑填入真实 MAIL_TO"
else
else
ok
"/etc/news-harvester/default.conf 已存在,保留不覆盖"
ok
"/etc/news-harvester/default.conf 已存在,保留不覆盖"
...
@@ -247,7 +251,7 @@ echo "========================================"
...
@@ -247,7 +251,7 @@ echo "========================================"
echo
""
echo
""
echo
"后续操作:"
echo
"后续操作:"
echo
" 1. 编辑配置: sudo vim /etc/news-harvester/default.conf"
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
" 3. 查看定时器: systemctl list-timers 'news-harvester@*' news-harvester-digest.timer"
echo
" 4. 手动测试: sudo systemctl start news-harvester@nikkei-world.service"
echo
" 4. 手动测试: sudo systemctl start news-harvester@nikkei-world.service"
echo
" 5. 查看日志: journalctl -u 'news-harvester@*' --since today -f"
echo
" 5. 查看日志: journalctl -u 'news-harvester@*' --since today -f"
...
...
modify-source.sh
→
bin/
modify-source.sh
View file @
216de72b
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
# modify-source.sh — 修改已有栏目的定时运行时间(及可选配置)
# modify-source.sh — 修改已有栏目的定时运行时间(及可选配置)
#
#
# 用法:
# 用法:
# sudo ./modify-source.sh <source-name> [选项]
# sudo ./
bin/
modify-source.sh <source-name> [选项]
#
#
# 选项:
# 选项:
# --time HH:MM 新的运行时间,如 10:30(必填)
# --time HH:MM 新的运行时间,如 10:30(必填)
...
@@ -15,8 +15,8 @@
...
@@ -15,8 +15,8 @@
# --help, -h 显示帮助
# --help, -h 显示帮助
#
#
# 示例:
# 示例:
# sudo ./modify-source.sh bloomberg --time 10:30
# sudo ./
bin/
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 nikkei-asia --time 14:00 --count 20 --mail-on-success 0
#
#
# 环境变量(可覆盖默认路径):
# 环境变量(可覆盖默认路径):
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
# SYSTEMD_DIR systemd unit 目录(默认 /etc/systemd/system)
...
@@ -102,15 +102,17 @@ DROPIN_DIR="$SYSTEMD_DIR/${TIMER_NAME}.d"
...
@@ -102,15 +102,17 @@ DROPIN_DIR="$SYSTEMD_DIR/${TIMER_NAME}.d"
# --- 读取当前生效时间(优先 systemctl show,回退读文件) ---
# --- 读取当前生效时间(优先 systemctl show,回退读文件) ---
CURRENT_TIME
=
""
CURRENT_TIME
=
""
# 方法1:从 systemctl show 读实际生效值(能反映 drop-in 覆盖)
# 方法1:从 systemctl show 读实际生效值(能反映 drop-in 覆盖)
local_show
=
$(
systemctl show
"
$TIMER_NAME
"
-p
OnCalendar 2>/dev/null
||
true
)
# 先读直接属性 OnCalendar,为空再从 TimersCalendar={ OnCalendar=... } 中匹配
if
[
-n
"
$local_show
"
]
&&
[
"
$local_show
"
!=
"OnCalendar="
]
;
then
local_show
=
$(
systemctl show
"
$TIMER_NAME
"
-p
OnCalendar
-p
TimersCalendar 2>/dev/null
||
true
)
CURRENT_TIME
=
$(
echo
"
$local_show
"
|
grep
-oE
'[0-9]{2}:[0-9]{2}:[0-9]{2}'
|
head
-1
||
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
fi
CURRENT_TIME
=
$(
echo
"
$local_cal
"
|
grep
-oE
'[0-9]{2}:[0-9]{2}'
|
head
-1
||
true
)
# 方法2:回退到直接读 timer 文件
# 方法2:回退到直接读 timer 文件
if
[
-z
"
$CURRENT_TIME
"
]
;
then
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
fi
[
-n
"
$CURRENT_TIME
"
]
&&
CURRENT_TIME
=
"
${
CURRENT_TIME
%
:
*
}
"
# 取 HH:MM
[
-z
"
$CURRENT_TIME
"
]
&&
CURRENT_TIME
=
"(未知)"
[
-z
"
$CURRENT_TIME
"
]
&&
CURRENT_TIME
=
"(未知)"
# --- 检查是否有 drop-in 覆盖(若有,改主文件不生效,需要先清除) ---
# --- 检查是否有 drop-in 覆盖(若有,改主文件不生效,需要先清除) ---
...
...
bin/status.sh
View file @
216de72b
...
@@ -35,6 +35,19 @@ EOF
...
@@ -35,6 +35,19 @@ EOF
hr
()
{
echo
"------------------------------------------------------------"
;
}
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() {
...
@@ -110,7 +123,7 @@ show_overview() {
if
[
-z
"
$timer_names
"
]
;
then
if
[
-z
"
$timer_names
"
]
;
then
echo
"未发现任何 news-harvester timer。"
echo
"未发现任何 news-harvester timer。"
echo
"提示:用 sudo
.
/add-source.sh <source> 添加栏目"
echo
"提示:用 sudo
~/news-harvester-pro/bin
/add-source.sh <source> 添加栏目"
return
return
fi
fi
...
@@ -130,26 +143,25 @@ show_overview() {
...
@@ -130,26 +143,25 @@ show_overview() {
sub
=
$(
systemctl is-active
"
$name
"
2>/dev/null
||
echo
"unknown"
)
sub
=
$(
systemctl is-active
"
$name
"
2>/dev/null
||
echo
"unknown"
)
# 获取 OnCalendar / NextElapse / LastTrigger
# 获取 OnCalendar / NextElapse / LastTrigger
# OnCalendar 可能返回多行(drop-in 覆盖时),用 grep 取所有值
local
on_cal next_el last_tr
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-
)
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-
)
last_tr
=
$(
systemctl show
"
$name
"
-p
LastTriggerUSec 2>/dev/null |
cut
-d
=
-f2-
)
# 提取 HH:MM 排序键(从 OnCalendar 或 NextElapse 兜底)
# 提取 HH:MM 排序键(从 OnCalendar 或 NextElapse 兜底)
# OnCalendar 格式: *-*-* 09:00:00
# OnCalendar 格式: *-*-* 09:00:00
或 *-*-* 09:00(秒可省略)
# NextElapse 格式: Wed 2026-08-27 09:00:00 CST
# NextElapse 格式: Wed 2026-08-27 09:00:00 CST
local
short sort_key run_fmt
local
short sort_key run_fmt
# 先从 OnCalendar 提取
# 先从 OnCalendar 提取
(直接取 HH:MM,兼容带秒/不带秒两种写法)
short
=
$(
echo
"
$on_cal
"
|
grep
-oE
'[0-9]{2}:[0-9]{2}
:[0-9]{2}
'
|
head
-1
)
short
=
$(
echo
"
$on_cal
"
|
grep
-oE
'[0-9]{2}:[0-9]{2}'
|
head
-1
)
# 如果 OnCalendar 没有,从 NextElapse 提取(去掉星期和日期,只留时间)
# 如果 OnCalendar 没有,从 NextElapse 提取(去掉星期和日期,只留时间)
if
[
-z
"
$short
"
]
&&
[
-n
"
$next_el
"
]
&&
[
"
$next_el
"
!=
"n/a"
]
;
then
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
fi
if
[
-n
"
$short
"
]
;
then
if
[
-n
"
$short
"
]
;
then
sort_key
=
"
$
{
short
%
:
*
}
"
# HH:MM
sort_key
=
"
$
short
"
run_fmt
=
"
$
{
short
%
:
*
}
"
# HH:MM
run_fmt
=
"
$
short
"
else
else
sort_key
=
"99:99"
sort_key
=
"99:99"
run_fmt
=
"-"
run_fmt
=
"-"
...
@@ -207,7 +219,7 @@ show_source_detail() {
...
@@ -207,7 +219,7 @@ show_source_detail() {
if
systemctl
cat
"
$timer_name
"
>
/dev/null 2>&1
;
then
if
systemctl
cat
"
$timer_name
"
>
/dev/null 2>&1
;
then
echo
"Timer:"
echo
"Timer:"
local
on_cal next_el last_tr
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-
)
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-
)
last_tr
=
$(
systemctl show
"
$timer_name
"
-p
LastTriggerUSec 2>/dev/null |
cut
-d
=
-f2-
)
...
...
web/app.py
View file @
216de72b
...
@@ -149,25 +149,36 @@ def get_timer_files():
...
@@ -149,25 +149,36 @@ def get_timer_files():
def
parse_timer_file
(
timer_path
):
def
parse_timer_file
(
timer_path
):
"""从 timer 文件解析 OnCalendar 时间"""
"""从 timer 文件解析 OnCalendar 时间"""
text
=
timer_path
.
read_text
(
encoding
=
"utf-8"
,
errors
=
"replace"
)
try
:
m
=
re
.
search
(
r"^OnCalendar=.*?\d{2}:\d{2}:\d{2}"
,
text
,
re
.
MULTILINE
)
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
:
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
:
if
time_match
:
return
f
"{time_match.group(1)}:{time_match.group(2)}"
return
f
"{time_match.group(1)}:{time_match.group(2)}"
return
"-"
return
"-"
def
read_oncalendar_from_systemd
(
timer_name
):
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
:
try
:
out
=
subprocess
.
run
(
out
=
subprocess
.
run
(
[
"systemctl"
,
"show"
,
timer_name
,
"-p"
,
"OnCalendar"
],
[
"systemctl"
,
"show"
,
timer_name
,
"-p"
,
"OnCalendar"
,
"-p"
,
"TimersCalendar"
],
capture_output
=
True
,
text
=
True
,
timeout
=
5
,
capture_output
=
True
,
text
=
True
,
timeout
=
5
,
)
.
stdout
)
.
stdout
m
=
re
.
search
(
r"(\d{2}):(\d{2}):\d{2}"
,
out
)
specs
=
[
s
for
s
in
re
.
findall
(
r"^OnCalendar=(.*)$"
,
out
,
re
.
MULTILINE
)
if
s
.
strip
()]
if
m
:
if
not
specs
:
return
f
"{m.group(1)}:{m.group(2)}"
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
:
except
Exception
:
pass
pass
return
None
return
None
...
@@ -379,7 +390,18 @@ def index():
...
@@ -379,7 +390,18 @@ def index():
sources
=
get_source_list
()
sources
=
get_source_list
()
# digest timer 状态
# digest timer 状态
digest_props
=
get_systemd_props
(
"news-harvester-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 任务状态
digest_status
=
_check_digest_status
()
digest_status
=
_check_digest_status
()
...
@@ -395,7 +417,9 @@ def index():
...
@@ -395,7 +417,9 @@ def index():
return
render_template
(
"index.html"
,
sources
=
sources
,
return
render_template
(
"index.html"
,
sources
=
sources
,
digest_props
=
digest_props
,
digest_run_time
=
digest_run_time
,
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>"
)
@
app
.
route
(
"/source/<name>"
)
...
...
web/templates/index.html
View file @
216de72b
...
@@ -61,10 +61,12 @@
...
@@ -61,10 +61,12 @@
{% if s.provider %}
{% if s.provider %}
<span
class=
"badge ok"
>
{{ s.provider }}
</span>
<span
class=
"badge ok"
>
{{ s.provider }}
</span>
{% else %}
{% 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 %}
{% endif %}
{% if s.fallback_provider %}
{% if s.fallback_provider %}
<span
style=
"font-size:11px;color:#888"
title=
"备用 provider"
>
→ {{ s.fallback_provider }}
</span>
<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 %}
{% endif %}
</td>
</td>
<td>
{{ s.last }}
</td>
<td>
{{ s.last }}
</td>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment