nodeseek关键词pushplus推送

首先,在你的 Debian 12 服务器上创建一个新文件,比如 setup.sh
nano setup.sh
然后将我为你提供的完整脚本内容复制并粘贴到这个文件中
#!/bin/bash
#
# RSS Monitor 服务安装与管理脚本
# 经过错误修正与全面优化,增强了安全性、健壮性和用户体验
#
# --- 脚本设置 ---
# set -e: 当任何命令返回非零退出码时立即退出
# set -u: 将任何未设置的变量视为错误
# set -o pipefail: 如果管道中的任何命令失败,则整个管道的返回值均为失败
set -euo pipefail
# --- 全局常量与配置 ---
# 使用大写只读变量来定义全局常量,这是一种良好的编程实践
readonly SERVICE="rss_monitor"
readonly INSTALL_DIR="/opt/${SERVICE}"
readonly VENV_DIR="${INSTALL_DIR}/venv"
readonly BIN_FILE="/usr/local/bin/rssctl"
readonly RK_FILE="/usr/local/bin/rk"
readonly BASH_COMPLETION_DIR="/etc/bash_completion.d"
readonly ZSH_COMPLETION_DIR="/usr/share/zsh/site-functions"
readonly CONFIG_FILE="${INSTALL_DIR}/config.json"
readonly MONITOR_SCRIPT="${INSTALL_DIR}/rss_monitor.py"
readonly UNINSTALLER_SCRIPT="${INSTALL_DIR}/uninstall.sh"
# --- 辅助函数 ---
# 封装日志函数,使输出更清晰、更统一
info() { echo -e "\033[32m[信息]\033[0m $1"; }
warn() { echo -e "\033[33m[警告]\033[0m $1" >&2; }
error() { echo -e "\033[31m[错误]\033[0m $1" >&2; exit 1; }
# 检查脚本是否以 root 权限运行
check_root() {
# ${EUID} 是一个环境变量,代表有效用户ID。0 代表 root 用户。
if [[ "${EUID}" -ne 0 ]]; then
error "此脚本必须以 root 权限 (sudo) 运行。"
fi
}
# --- 核心安装功能 ---
install_dependencies() {
info "=== [1/9] 安装系统依赖 ==="
# 使用 apt-get -qq 选项来减少不必要的输出,让安装过程更整洁
if ! apt-get update -qq 2>/dev/null || ! apt-get install -y -qq python3 python3-pip python3-venv bash-completion >/dev/null 2>&1; then
error "依赖安装失败。请尝试手动运行 'apt-get update' 后重试。"
fi
}
setup_directories_and_venv() {
info "=== [2/9] 配置程序目录与 Python 虚拟环境 ==="
# 【安全修复】创建一个专用的系统用户来运行服务。
# 这个用户没有 shell 登录权限 (-s /bin/false),也无法作为普通用户登录。
if ! id -u "${SERVICE}" &>/dev/null; then
info "创建专用的系统用户 '${SERVICE}'..."
useradd -r -s /bin/false -d "${INSTALL_DIR}" "${SERVICE}"
fi
mkdir -p "${INSTALL_DIR}"
info "创建 Python 虚拟环境..."
python3 -m venv "${VENV_DIR}"
info "安装 Python 依赖库 (requests, feedparser)..."
# 将依赖安装到虚拟环境中,避免污染全局 Python 环境。
if ! "${VENV_DIR}/bin/pip" install -q --upgrade pip || \
! "${VENV_DIR}/bin/pip" install -q requests feedparser; then
error "在虚拟环境中安装 Python 依赖失败。"
fi
# 【安全修复】将整个程序目录的所有权交给新创建的服务用户
chown -R "${SERVICE}:${SERVICE}" "${INSTALL_DIR}"
}
create_config_file() {
info "=== [3/9] 写入示例配置文件 ==="
cat > "${CONFIG_FILE}" <<EOF
{
"interval": 15,
"keywords": ["AI", "ChatGPT", "开源"],
"pushplus_token": "请在这里填写你的pushplus token"
}
EOF
chown "${SERVICE}:${SERVICE}" "${CONFIG_FILE}"
info "配置文件已创建于: ${CONFIG_FILE}"
warn "请记得修改配置文件,填入你自己的 PushPlus Token!"
}
create_monitor_script() {
info "=== [4/9] 写入主监控脚本 (rss_monitor.py) ==="
cat > "${MONITOR_SCRIPT}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import json
import time
import signal
import requests
import feedparser
import sys
# --- 全局常量 ---
CONFIG_FILE = "/opt/rss_monitor/config.json"
RSS_URL = "https://rss.nodeseek.com"
# --- 全局状态变量 ---
running = True
reload_config_flag = False
def log(message):
"""将日志信息打印到标准输出,由 systemd 统一管理"""
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"[{timestamp}] {message}", flush=True)
def load_config():
"""加载并返回 JSON 配置文件内容"""
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
log(f"错误:无法加载或解析配置文件: {e}")
return None
def send_pushplus(token, title, content):
"""通过 PushPlus 发送通知"""
if not token or token == "请在这里填写你的pushplus token":
log("警告:PushPlus token 未配置,跳过发送。")
return
url = "http://www.pushplus.plus/send"
data = {
"token": token,
"title": title,
"content": content,
"template": "markdown",
}
try:
response = requests.post(url, json=data, timeout=15)
response.raise_for_status()
log(f"成功发送通知: {title}")
except requests.exceptions.RequestException as e:
log(f"错误:PushPlus 通知发送失败: {e}")
def sigterm_handler(signum, frame):
global running
log("收到 SIGTERM 信号,服务准备停止...")
running = False
def sighup_handler(signum, frame):
global reload_config_flag
log("收到 SIGHUP 信号,将重新加载配置...")
reload_config_flag = True
signal.signal(signal.SIGTERM, sigterm_handler)
signal.signal(signal.SIGHUP, sighup_handler)
def main():
global reload_config_flag
log("RSS Monitor 服务已启动。")
config = load_config()
if not config:
log("错误:启动时无法加载配置,服务退出。")
sys.exit(1)
seen_links = set()
while running:
try:
if reload_config_flag:
new_config = load_config()
if new_config:
config = new_config
log("配置已成功重新加载。")
else:
log("警告:尝试重载配置失败,继续使用旧配置。")
reload_config_flag = False
feed = feedparser.parse(RSS_URL)
if feed.bozo:
log(f"警告:RSS feed 解析可能存在问题: {feed.bozo_exception}")
keywords = {kw.lower() for kw in config.get("keywords", [])}
for entry in reversed(feed.entries):
if entry.link not in seen_links:
seen_links.add(entry.link)
entry_title_lower = entry.title.lower()
if any(kw in entry_title_lower for kw in keywords):
log(f"发现关键词匹配: '{entry.title}'")
# 【错误修复】使用 .get() 方法安全地获取 summary,避免因缺少该字段而崩溃
summary_text = entry.get("summary", "(无摘要)")
content = f"**摘要**: {summary_text[:150]}...\n\n[点击查看原文]({entry.link})"
send_pushplus(config.get("pushplus_token"), entry.title, content)
time.sleep(config.get("interval", 15))
except Exception as e:
log(f"错误:主循环发生未知异常: {e}")
time.sleep(60)
log("RSS Monitor 服务已停止。")
if __name__ == "__main__":
main()
EOF
chmod +x "${MONITOR_SCRIPT}"
}
create_control_tool() {
info "=== [5/9] 安装管理工具 (rssctl) ==="
cat > "${BIN_FILE}" <<'EOF'
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import json
import subprocess
# --- 全局常量 ---
SERVICE_NAME_CONST = "rss_monitor"
CONFIG_FILE = f"/opt/{SERVICE_NAME_CONST}/config.json"
UNINSTALLER_SCRIPT = f"/opt/{SERVICE_NAME_CONST}/uninstall.sh"
SERVICE_FILE = f"{SERVICE_NAME_CONST}.service"
# --- 颜色常量 ---
class C:
GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[31m"
RESET = "\033[0m"
def run_system_command(command, **kwargs):
"""安全地执行一个系统命令"""
try:
result = subprocess.run(command, check=True, text=True, capture_output=True, **kwargs)
return result.stdout.strip()
except FileNotFoundError:
print(f"{C.RED}[错误]{C.RESET} 命令 '{command[0]}' 未找到。", file=sys.stderr)
sys.exit(1)
except subprocess.CalledProcessError as e:
print(f"{C.RED}[错误]{C.RESET} 执行命令 '{' '.join(command)}' 失败:", file=sys.stderr)
print(e.stderr, file=sys.stderr)
sys.exit(1)
def reload_service():
"""通知 systemd 服务重载配置"""
print("正在通知服务重新加载配置...")
run_system_command(["systemctl", "kill", "-s", "HUP", SERVICE_FILE])
print(f"{C.GREEN}服务已收到重载指令。{C.RESET}")
def load_config():
"""加载 JSON 配置文件"""
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"{C.RED}[错误]{C.RESET} 配置文件 '{CONFIG_FILE}' 未找到。", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print(f"{C.RED}[错误]{C.RESET} 配置文件 '{CONFIG_FILE}' 格式无效。", file=sys.stderr)
sys.exit(1)
def save_config(cfg):
"""保存配置到 JSON 文件"""
try:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(cfg, f, ensure_ascii=False, indent=2)
except IOError as e:
print(f"{C.RED}[错误]{C.RESET} 无法写入配置文件: {e}", file=sys.stderr)
sys.exit(1)
def list_keywords(cfg):
"""列出带编号的关键词列表"""
keywords = sorted(cfg.get("keywords", []))
if not keywords:
print(f"{C.YELLOW}当前没有配置任何关键词。{C.RESET}")
else:
print(f"{C.GREEN}当前关键词列表:{C.RESET}")
for i, kw in enumerate(keywords, 1):
print(f" {i}. {kw}")
return keywords
def add_keyword(cfg, keyword):
"""为命令行非交互模式保留的单个添加功能"""
keywords = set(cfg.get("keywords", []))
if keyword in keywords:
print(f"{C.YELLOW}关键词 '{keyword}' 已存在。{C.RESET}")
return
keywords.add(keyword)
cfg["keywords"] = sorted(list(keywords))
save_config(cfg)
print(f"{C.GREEN}成功添加关键词: {keyword}{C.RESET}")
reload_service()
def add_keywords(cfg, keywords_to_add):
"""批量添加关键词"""
current_keywords = set(cfg.get("keywords", []))
added, existed = [], []
for kw in keywords_to_add:
if kw not in current_keywords:
current_keywords.add(kw)
added.append(kw)
else:
existed.append(kw)
if added:
cfg["keywords"] = sorted(list(current_keywords))
save_config(cfg)
print(f"\n{C.GREEN}成功添加 {len(added)} 个新关键词: {', '.join(added)}{C.RESET}")
reload_service()
else:
print(f"\n{C.YELLOW}没有新的关键词被添加。{C.RESET}")
if existed:
print(f"{C.YELLOW}下列 {len(existed)} 个关键词已存在,已跳过: {', '.join(existed)}{C.RESET}")
def remove_keyword(cfg, keyword):
"""为命令行非交互模式保留的单个删除功能"""
keywords = cfg.get("keywords", [])
if keyword not in keywords:
print(f"{C.YELLOW}关键词 '{keyword}' 不存在。{C.RESET}")
return
keywords.remove(keyword)
cfg["keywords"] = keywords
save_config(cfg)
print(f"{C.GREEN}成功删除关键词: {keyword}{C.RESET}")
reload_service()
def remove_keywords(cfg, keywords_to_remove):
"""批量删除关键词"""
current_keywords = set(cfg.get("keywords", []))
removed_set = current_keywords.intersection(set(keywords_to_remove))
current_keywords.difference_update(removed_set)
if removed_set:
cfg["keywords"] = sorted(list(current_keywords))
save_config(cfg)
print(f"\n{C.GREEN}成功删除 {len(removed_set)} 个关键词: {', '.join(sorted(list(removed_set)))}{C.RESET}")
reload_service()
else:
print(f"\n{C.YELLOW}没有关键词被删除。{C.RESET}")
def set_token(cfg, token):
if not token:
print(f"{C.RED}[错误]{C.RESET} token 不能为空。")
return
cfg['pushplus_token'] = token
save_config(cfg)
print(f"{C.GREEN}PushPlus token 已成功更新。{C.RESET}")
reload_service()
def set_interval(cfg, interval_str):
"""设置检索时间间隔"""
try:
interval = int(interval_str)
if interval < 10:
print(f"\n{C.RED}[错误]{C.RESET} 检索间隔不能低于10秒,以避免对目标网站造成过高负载。")
return
cfg['interval'] = interval
save_config(cfg)
print(f"\n{C.GREEN}检索时间间隔已成功更新为 {interval} 秒。{C.RESET}")
reload_service()
except ValueError:
print(f"\n{C.RED}[错误]{C.RESET} 输入无效,请输入一个纯数字。")
def uninstall_service():
"""处理卸载逻辑"""
if os.geteuid() != 0:
print(f"{C.RED}[错误]{C.RESET} 卸载操作需要 root 权限。")
print(f"{C.YELLOW}请使用 'sudo rkm' 或 'sudo rssctl menu' 再次运行此命令。{C.RESET}")
return
print(f"\n{C.RED}" + "="*50)
print("!!! 危险操作警告 !!!")
print(f"您即将彻底卸载 '{SERVICE_NAME_CONST}' 服务。")
print("此操作将会:\n- 停止并禁用服务\n- 删除所有程序文件、配置文件和日志\n- 删除专为此服务创建的系统用户")
print("此操作一旦执行,将无法撤销!")
print("="*50 + f"{C.RESET}\n")
try:
confirm = input(f"{C.YELLOW}为防止误操作,请输入服务的名称 '{SERVICE_NAME_CONST}' 来确认卸载: {C.RESET}").strip()
if confirm == SERVICE_NAME_CONST:
print("正在执行卸载脚本...")
subprocess.run(['bash', UNINSTALLER_SCRIPT, 'uninstall'], check=True)
print(f"{C.GREEN}服务已成功卸载。{C.RESET}")
else:
print(f"{C.YELLOW}输入不匹配,卸载操作已取消。{C.RESET}")
except (KeyboardInterrupt, EOFError):
print(f"\n{C.YELLOW}操作已取消。{C.RESET}")
def show_menu():
"""显示交互式管理菜单"""
while True:
print(f"\n{C.GREEN}=== RSS Monitor 服务管理菜单 ===")
print("1. 查看关键词列表")
print("2. 添加关键词")
print("3. 删除关键词")
print("4. 修改 PushPlus Token")
print("5. 修改检索间隔")
print("-------------------------------")
print("6. 查看服务状态")
print("7. 重启服务")
print("8. 查看实时日志")
print("-------------------------------")
print(f"{C.YELLOW}9. 卸载服务 (危险操作){C.GREEN}")
print("0. 退出菜单")
print("===============================" + C.RESET)
choice = input(f"{C.GREEN}请输入选项序号: {C.RESET}").strip()
if choice == '1':
list_keywords(load_config())
elif choice == '2':
cfg = load_config()
list_keywords(cfg)
user_input = input("\n请输入一个或多个要添加的关键词 (用空格分隔, 例如: 科技 财经): ").strip()
if user_input:
keywords = [kw for kw in user_input.split(' ') if kw]
if keywords: add_keywords(cfg, keywords)
elif choice == '3':
cfg = load_config()
keywords_list = list_keywords(cfg)
if not keywords_list: continue
user_input = input("\n请输入一个或多个要删除的关键词编号 (用空格分隔, 例如: 1 3 5): ").strip()
if not user_input:
print(f"{C.YELLOW}操作已取消。{C.RESET}")
continue
nums_to_delete, invalid_inputs = set(), []
for num_str in user_input.split(' '):
if not num_str: continue
try:
num = int(num_str)
if 1 <= num <= len(keywords_list): nums_to_delete.add(num)
else: invalid_inputs.append(num_str)
except ValueError:
invalid_inputs.append(num_str)
if invalid_inputs: print(f"\n{C.RED}[错误]{C.RESET} 以下输入是无效的或超出范围: {', '.join(invalid_inputs)}")
if not nums_to_delete: continue
keywords_to_delete = [keywords_list[i-1] for i in sorted(list(nums_to_delete))]
print("\n你确定要删除以下关键词吗?")
for kw in keywords_to_delete: print(f"- {kw}")
confirm = input(f"{C.YELLOW}请输入 'y' 确认: {C.RESET}").strip().lower()
if confirm == 'y': remove_keywords(cfg, keywords_to_delete)
else: print(f"{C.YELLOW}删除操作已取消。{C.RESET}")
elif choice == '4':
token = input("请输入新的 PushPlus Token: ").strip()
if token: set_token(load_config(), token)
elif choice == '5':
cfg = load_config()
current_interval = cfg.get('interval', 15)
print(f"当前检索间隔为: {current_interval} 秒 (约 {current_interval/60:.2f} 分钟)")
new_interval = input(f"请输入新的检索间隔 (秒, {C.YELLOW}最低10秒, 请谨慎设置{C.RESET}): ").strip()
if new_interval: set_interval(cfg, new_interval)
elif choice == '6':
os.system(f"systemctl status {SERVICE_FILE}")
elif choice == '7':
print("正在重启服务...")
run_system_command(["systemctl", "restart", SERVICE_FILE])
print(f"{C.GREEN}服务已重启。{C.RESET}")
elif choice == '8':
print("按 Ctrl+C 退出日志查看。")
try:
os.system(f"journalctl -u {SERVICE_FILE} -f -n 50")
except KeyboardInterrupt:
print("\n已退出日志查看。")
elif choice == '9':
uninstall_service()
elif choice == '0':
print("已退出管理菜单。")
break
else:
print(f"{C.RED}[错误]{C.RESET} 无效输入,请输入菜单中的数字。")
def usage():
print(f"{C.GREEN}用法: {os.path.basename(sys.argv[0])} [命令] [参数]")
print("\n可用命令:")
print(" list-keywords - 列出所有关键词")
print(" add-keyword <关键词> - 添加一个关键词")
print(" remove-keyword <关键词> - (非交互模式) 按名称删除一个关键词")
print(" set-token <Token> - 设置 PushPlus token")
print(" set-interval <秒数> - 设置检索时间间隔")
print(" reload - 重新加载服务配置")
print(f" menu - 显示交互式管理菜单{C.RESET}")
def main():
if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ['-h', '--help']):
show_menu()
sys.exit(0)
cmd = sys.argv[1]
if cmd == "menu": show_menu()
elif cmd == "reload": reload_service()
elif cmd in ["list-keywords", "add-keyword", "remove-keyword", "set-token", "set-interval"]:
cfg = load_config()
if cmd == "list-keywords": list_keywords(cfg)
elif cmd in ["add-keyword", "remove-keyword", "set-token", "set-interval"]:
if len(sys.argv) != 3:
print(f"{C.RED}[错误]{C.RESET} 命令 '{cmd}' 需要一个参数。"); usage(); sys.exit(1)
arg = sys.argv[2]
if cmd == "add-keyword": add_keyword(cfg, arg)
elif cmd == "remove-keyword": remove_keyword(cfg, arg)
elif cmd == "set-token": set_token(cfg, arg)
elif cmd == "set-interval": set_interval(cfg, arg)
else:
usage()
if __name__ == "__main__":
main()
EOF
chmod +x "${BIN_FILE}"
}
create_systemd_service() {
info "=== [6/9] 创建 systemd 服务 ==="
cat > "/etc/systemd/system/${SERVICE}.service" <<EOF
[Unit]
Description=RSS Monitor Service
After=network.target
[Service]
Type=simple
# 【安全修复】指定服务以我们创建的低权限用户运行
User=${SERVICE}
Group=${SERVICE}
WorkingDirectory=${INSTALL_DIR}
ExecStart=${VENV_DIR}/bin/python3 ${MONITOR_SCRIPT}
Restart=always
RestartSec=10
# 【健壮性改进】将日志重定向到 systemd journald,方便管理
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
info "重新加载 systemd 配置并启动服务..."
systemctl daemon-reload
systemctl enable "${SERVICE}.service"
systemctl restart "${SERVICE}.service"
}
setup_shell_integration() {
info "=== [7/9] 配置 Shell 自动补全与快捷别名 ==="
cat > "${BASH_COMPLETION_DIR}/rssctl" <<'EOF'
_rssctl_completion() {
local cur prev
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ ${COMP_CWORD} -eq 1 ]]; then
COMPREPLY=( $(compgen -W "list-keywords add-keyword remove-keyword set-token set-interval reload menu" -- "${cur}") )
return 0
fi
if [[ ${prev} == "remove-keyword" ]]; then
# 在非交互模式下,补全是基于关键词名称的
local keywords=$(rssctl list-keywords 2>/dev/null | grep -E '^\s+[0-9]+\.' | sed -E 's/^\s+[0-9]+\. //')
COMPREPLY=( $(compgen -W "${keywords}" -- "${cur}") )
fi
}
complete -F _rssctl_completion rssctl rk
EOF
mkdir -p "${ZSH_COMPLETION_DIR}"
ln -sf "${BASH_COMPLETION_DIR}/rssctl" "${ZSH_COMPLETION_DIR}/_rssctl"
info "=== [8/9] 创建快捷命令 (rk) 与别名 ==="
ln -sf "${BIN_FILE}" "${RK_FILE}"
local aliases_snippet
aliases_snippet=$(cat <<'EOF'
# RSS Monitor 快捷别名
alias rk='rssctl'
alias rkl='rssctl list-keywords'
alias rka='rssctl add-keyword'
alias rkr='rssctl remove-keyword'
alias rkm='rssctl menu'
EOF
)
# 遍历当前用户和 root 用户的主目录,为 bash 和 zsh 添加别名
for homedir in "/root" "${HOME:-/root}"; do
for rc_file in "${homedir}/.bashrc" "${homedir}/.zshrc"; do
if [ -f "${rc_file}" ] && ! grep -q "# RSS Monitor 快捷别名" "${rc_file}"; then
info "添加别名到 ${rc_file}..."
echo "${aliases_snippet}" >> "${rc_file}"
fi
done
done
}
backup_uninstaller() {
info "=== [9/9] 备份卸载程序以便管理 ==="
# 将此脚本自身复制到安装目录,以便 rssctl 可以调用它来卸载
cp "$0" "${UNINSTALLER_SCRIPT}"
chmod +x "${UNINSTALLER_SCRIPT}"
}
# --- 完整安装流程 ---
install_all() {
check_root
info "开始安装 RSS Monitor 服务..."
install_dependencies
setup_directories_and_venv
create_config_file
create_monitor_script
create_control_tool
create_systemd_service
setup_shell_integration
backup_uninstaller
info "=========================================="
info "🎉 RSS Monitor 安装完成!"
info "=========================================="
echo ""
info "请执行 'source ~/.bashrc' 或 'source ~/.zshrc' 来让快捷别名生效,或重新打开终端。"
echo ""
info "常用命令:"
info " rkm - 显示交互式管理菜单 (推荐)"
info " systemctl status ${SERVICE} - 查看服务运行状态"
info " journalctl -u ${SERVICE} -f - 查看实时日志"
echo ""
"${BIN_FILE}" menu
}
# --- 卸载功能 ---
uninstall_all() {
check_root
warn "即将彻底卸载 RSS Monitor 服务!"
# 在非交互式卸载时,也需要确认
if [[ $- == *i* ]]; then # 检查是否在交互式 shell 中
read -p "这将删除所有配置文件、脚本和专用用户,确定要继续吗?(y/N): " choice
if [[ "${choice,,}" != "y" ]]; then # 转换为小写比较
info "操作已取消。"
exit 0
fi
fi
info "停止并禁用 systemd 服务..."
systemctl stop "${SERVICE}.service" &>/dev/null || true
systemctl disable "${SERVICE}.service" &>/dev/null || true
rm -f "/etc/systemd/system/${SERVICE}.service"
systemctl daemon-reload
info "删除程序目录: ${INSTALL_DIR}..."
rm -rf "${INSTALL_DIR}"
info "删除命令、软链接和自动补全脚本..."
rm -f "${BIN_FILE}" "${RK_FILE}" "${BASH_COMPLETION_DIR}/rssctl" "${ZSH_COMPLETION_DIR}/_rssctl"
info "从 shell 配置文件中移除别名..."
for homedir in "/root" "${HOME:-/root}"; do
for rc_file in "${homedir}/.bashrc" "${homedir}/.zshrc"; do
if [ -f "${rc_file}" ]; then
# 使用 sed 命令来删除我们之前添加的别名区块
sed -i '/# RSS Monitor 快捷别名/,+5d' "${rc_file}"
fi
done
done
if id -u "${SERVICE}" &>/dev/null; then
info "删除专用用户 '${SERVICE}'..."
userdel "${SERVICE}"
fi
info "卸载完成。"
}
# --- 脚本入口 ---
usage() {
echo "用法: $0 [install|uninstall]"
echo " install: 安装或更新 RSS Monitor 服务"
echo " uninstall: 卸载 RSS Monitor 服务"
exit 1
}
main() {
if [ "$#" -ne 1 ]; then
usage
fi
case "$1" in
install)
install_all
;;
uninstall)
uninstall_all
;;
*)
usage
;;
esac
}
# 执行主函数,并将所有参数传递给它
main "$@"
赋予脚本执行权限
chmod +x setup.sh
运行安装脚本,脚本会自动完成所有安装步骤,包括安装依赖、创建目录、写入服务文件等
./setup.sh install
配置服务 安装完成后,你需要修改配置文件,填入你的 PushPlus Token 和关键词
nano /opt/rss_monitor/config.json
脚本其他命令
#脚本管理菜单快捷键
rkm
#查看脚本运行状态
systemctl status rss_monitor
#停止运行脚本
systemctl stop rss_monitor
#开始运行脚本
systemctl start rss_monitor
#卸载脚本
./setup.sh uninstall