1. 创建脚本文件 rss.sh

nano /root/rss.sh

rss.sh 脚本内容:

#!/bin/bash
# ============================================================
# rss.sh — xBlog RSS 订阅管理脚本
# 使用方式:bash /root/rss.sh
# ============================================================

# ============================
# 配置区域(按需修改)
# ============================
SITE_DIR="/opt/1panel/www/sites/xblog/index"   # 网站根目录(宿主机路径)
SITE_DIR_CONTAINER="/www/sites/xblog/index"    # 网站根目录(容器内路径)
SITE_URL="https://xblog.itxgo.com"             # 网站域名,末尾不加斜杠
BLOG_TITLE="iTxGo🍃 - xBlog™️"                # 网站标题
BLOG_DESC="PHP + SQLite 简约博客"               # 网站描述
RSS_LIMIT=20                                   # RSS 收录文章数量
RSS_SECRET="xblog_rss"                         # 手动触发密钥
BLOG_EMAIL="163@163.com"                       # 作者邮箱(用于 RSS author 字段)
FILE_OWNER="1000:1000"                         # 网站文件所有者(与其他网站文件保持一致)
PHP_BIN="docker exec php php"                  # PHP 执行命令(容器环境)
SCRIPT_PATH="$(realpath "$0")"                 # 本脚本自身路径(卸载时使用)
# ============================

# 派生路径(无需修改)
PHP_SCRIPT="$SITE_DIR/generate_rss.php"
RSS_FILE="$SITE_DIR/rss.xml"
DB_FILE="$SITE_DIR/blog.db"

# ============================================================
# 颜色输出
# ============================================================
GREEN="\033[0;32m"
RED="\033[0;31m"
YELLOW="\033[1;33m"
CYAN="\033[0;36m"
RESET="\033[0m"

ok()   { echo -e "${GREEN}✅ $1${RESET}"; }
err()  { echo -e "${RED}❌ $1${RESET}"; }
info() { echo -e "${CYAN}ℹ️  $1${RESET}"; }
warn() { echo -e "${YELLOW}⚠️  $1${RESET}"; }

# ============================================================
# 功能函数
# ============================================================

# 写入 generate_rss.php
write_php() {
    cat > "$PHP_SCRIPT" << PHPEOF
<?php
/**
 * generate_rss.php — RSS 订阅生成脚本
 * 由 rss.sh 自动生成,请勿手动修改配置项
 */

define('RSS_BASE_URL',  '${SITE_URL}');
define('RSS_OUTPUT',    __DIR__ . '/rss.xml');
define('RSS_SECRET',    '${RSS_SECRET}');
define('RSS_LIMIT',     ${RSS_LIMIT});
define('BLOG_TITLE',    '${BLOG_TITLE}');
define('BLOG_DESC',     '${BLOG_DESC}');
define('BLOG_EMAIL',    '${BLOG_EMAIL}');

function generateRss(): int|false
{
    \$dbPath = __DIR__ . '/blog.db';
    if (!file_exists(\$dbPath)) {
        error_log('[RSS] 数据库文件不存在: ' . \$dbPath);
        return false;
    }

    try {
        \$db = new PDO('sqlite:' . \$dbPath);
        \$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        \$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    } catch (PDOException \$e) {
        error_log('[RSS] 数据库连接失败: ' . \$e->getMessage());
        return false;
    }

    try {
        \$stmt = \$db->prepare(
            "SELECT a.id, a.title, a.excerpt, a.created_at, a.updated_at, u.username
             FROM articles a
             LEFT JOIN users u ON u.id = a.author_id
             ORDER BY a.created_at DESC
             LIMIT :limit"
        );
        \$stmt->bindValue(':limit', RSS_LIMIT, PDO::PARAM_INT);
        \$stmt->execute();
        \$articles = \$stmt->fetchAll();
    } catch (PDOException \$e) {
        error_log('[RSS] 文章查询失败: ' . \$e->getMessage());
        return false;
    }

    \$buildDate   = date(DATE_RSS);
    \$channelLink = RSS_BASE_URL . '/';
    \$feedLink    = RSS_BASE_URL . '/rss.xml';

    \$xml  = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    \$xml .= '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">' . "\n";
    \$xml .= "  <channel>\n";
    \$xml .= "    <title>"       . htmlspecialchars(BLOG_TITLE, ENT_XML1 | ENT_QUOTES, 'UTF-8') . "</title>\n";
    \$xml .= "    <link>"        . htmlspecialchars(\$channelLink, ENT_XML1 | ENT_QUOTES, 'UTF-8') . "</link>\n";
    \$xml .= "    <description>" . htmlspecialchars(BLOG_DESC, ENT_XML1 | ENT_QUOTES, 'UTF-8') . "</description>\n";
    \$xml .= "    <language>zh-CN</language>\n";
    \$xml .= "    <lastBuildDate>" . \$buildDate . "</lastBuildDate>\n";
    \$xml .= '    <atom:link href="' . htmlspecialchars(\$feedLink, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '" rel="self" type="application/rss+xml"/>' . "\n";

    foreach (\$articles as \$article) {
        \$link    = RSS_BASE_URL . '/article.php?id=' . (int)\$article['id'];
        \$title   = htmlspecialchars(\$article['title'] ?? '', ENT_XML1 | ENT_QUOTES, 'UTF-8');
        \$author  = htmlspecialchars(\$article['username'] ?? 'unknown', ENT_XML1 | ENT_QUOTES, 'UTF-8');
        \$desc    = htmlspecialchars(trim(\$article['excerpt'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8');
        \$pubDate = formatRssDate(\$article['created_at']);
        \$guid    = htmlspecialchars(\$link, ENT_XML1 | ENT_QUOTES, 'UTF-8');

        \$xml .= "    <item>\n";
        \$xml .= "      <title>{\$title}</title>\n";
        \$xml .= "      <link>{\$guid}</link>\n";
        \$xml .= "      <author>" . BLOG_EMAIL . " ({\$author})</author>\n";
        \$xml .= "      <description>{\$desc}</description>\n";
        \$xml .= "      <pubDate>{\$pubDate}</pubDate>\n";
        \$xml .= "      <guid isPermaLink=\"true\">{\$guid}</guid>\n";
        \$xml .= "    </item>\n";
    }

    \$xml .= "  </channel>\n";
    \$xml .= "</rss>\n";

    \$result = file_put_contents(RSS_OUTPUT, \$xml, LOCK_EX);
    if (\$result === false) {
        error_log('[RSS] 写入 rss.xml 失败,请检查目录权限: ' . RSS_OUTPUT);
        return false;
    }

    return count(\$articles);
}

function formatRssDate(?string \$datetime): string
{
    if (empty(\$datetime)) return date(DATE_RSS);
    \$ts = strtotime(\$datetime);
    return \$ts ? date(DATE_RSS, \$ts) : date(DATE_RSS);
}

if (basename(__FILE__) === basename(\$_SERVER['SCRIPT_FILENAME'] ?? '') || php_sapi_name() === 'cli') {
    if (php_sapi_name() === 'cli') {
        \$ok = generateRss();
        echo \$ok !== false
            ? "[RSS] 生成成功: " . RSS_OUTPUT . "(共 {\$ok} 篇文章)" . PHP_EOL
            : "[RSS] 生成失败,请检查错误日志。" . PHP_EOL;
        exit(\$ok !== false ? 0 : 1);
    }

    \$key = \$_GET['key'] ?? '';
    if (!hash_equals(RSS_SECRET, \$key)) {
        http_response_code(403);
        exit('403 Forbidden: 密钥错误');
    }

    \$ok = generateRss();
    header('Content-Type: text/plain; charset=utf-8');
    echo \$ok !== false
        ? "✅ rss.xml 已生成:" . RSS_OUTPUT . "\n📄 共收录 {\$ok} 篇文章"
        : "❌ 生成失败,请查看 PHP 错误日志。";
    exit();
}
PHPEOF
}

# 检查是否已安装
is_installed() {
    [ -f "$PHP_SCRIPT" ]
}

# ============================================================
# 菜单功能:1 安装 RSS
# ============================================================
do_install() {
    echo ""
    if is_installed; then
        warn "检测到已安装,请使用「2 更新 RSS」手动刷新,或直接重新安装(将覆盖现有文件)。"
        read -rp "是否继续重新安装?[y/N] " confirm
        [[ "$confirm" =~ ^[Yy]$ ]] || { info "已取消。"; return; }
    fi

    # 检查网站目录
    if [ ! -d "$SITE_DIR" ]; then
        err "网站目录不存在:$SITE_DIR"
        info "请检查脚本顶部 SITE_DIR 配置项。"
        return 1
    fi

    # 检查数据库
    if [ ! -f "$DB_FILE" ]; then
        err "未找到数据库文件:$DB_FILE"
        return 1
    fi

    info "正在写入 generate_rss.php ..."
    write_php && chown "$FILE_OWNER" "$PHP_SCRIPT" && ok "generate_rss.php 已写入" || { err "写入失败,请检查目录权限。"; return 1; }

    info "正在生成初始 rss.xml ..."
    $PHP_BIN "$SITE_DIR_CONTAINER/generate_rss.php"
    if [ -f "$RSS_FILE" ]; then
        chown "$FILE_OWNER" "$RSS_FILE"
        ok "rss.xml 已生成:$RSS_FILE"
    else
        err "rss.xml 生成失败,请检查 PHP 错误日志。"
    fi

    echo ""
    ok "安装完成!"
    info "请在 1panel 面板「计划任务」中添加定时更新任务"
    info "RSS 订阅地址:${SITE_URL}/rss.xml"
}

# ============================================================
# 菜单功能:2 更新 RSS
# ============================================================
do_update() {
    echo ""
    if ! is_installed; then
        err "尚未安装,请先选择「1 安装 RSS」。"
        return 1
    fi

    info "正在更新 rss.xml ..."
    $PHP_BIN "$SITE_DIR_CONTAINER/generate_rss.php"
    if [ -f "$RSS_FILE" ]; then
        ok "rss.xml 更新成功"
        info "文件路径:$RSS_FILE"
    else
        err "更新失败,请检查 PHP 错误日志。"
    fi
}

# ============================================================
# 菜单功能:3 卸载 RSS
# ============================================================
do_uninstall_rss() {
    echo ""
    if ! is_installed; then
        warn "未检测到已安装的 RSS 文件。"
        return
    fi

    warn "将删除以下文件:"
    echo "  - $PHP_SCRIPT"
    echo "  - $RSS_FILE"
    read -rp "确认卸载?[y/N] " confirm
    [[ "$confirm" =~ ^[Yy]$ ]] || { info "已取消。"; return; }

    [ -f "$PHP_SCRIPT" ] && rm -f "$PHP_SCRIPT" && ok "已删除 generate_rss.php"
    [ -f "$RSS_FILE" ]   && rm -f "$RSS_FILE"   && ok "已删除 rss.xml"

    echo ""
    ok "卸载完成。"
    warn "请记得在 1panel 面板「计划任务」中手动删除对应任务。"
}

# ============================================================
# 菜单功能:4 卸载脚本
# ============================================================
do_uninstall_script() {
    echo ""
    warn "将删除本脚本自身:$SCRIPT_PATH"
    if is_installed; then
        warn "检测到 RSS 仍已安装,建议先执行「3 卸载 RSS」。"
        read -rp "是否同时卸载 RSS?[y/N] " also_uninstall
        [[ "$also_uninstall" =~ ^[Yy]$ ]] && do_uninstall_rss
    fi

    read -rp "确认删除脚本?[y/N] " confirm
    [[ "$confirm" =~ ^[Yy]$ ]] || { info "已取消。"; return; }

    rm -f "$SCRIPT_PATH"
    ok "脚本已删除:$SCRIPT_PATH"
    exit 0
}

# ============================================================
# 菜单功能:5 使用说明
# ============================================================
do_help() {
    echo ""
    echo -e "${CYAN}=============================${RESET}"
    echo -e "${CYAN}          使用说明           ${RESET}"
    echo -e "${CYAN}=============================${RESET}"
    echo ""
    echo -e "${YELLOW}【脚本配置项】${RESET}(位于 rss.sh 顶部)"
    echo "  SITE_DIR              网站根目录路径(宿主机)"
    echo "  SITE_DIR_CONTAINER    网站根目录路径(容器内)"
    echo "  SITE_URL              博客域名,末尾不加斜杠"
    echo "  BLOG_TITLE            博客标题"
    echo "  BLOG_DESC             博客描述"
    echo "  RSS_LIMIT             RSS 收录文章数量(默认 20)"
    echo "  RSS_SECRET            手动触发更新的密钥"
    echo "  PHP_BIN               PHP 执行命令"
    echo ""
    echo -e "${YELLOW}【菜单功能】${RESET}"
    echo "  1. 安装 RSS   写入 generate_rss.php 并立即生成 rss.xml"
    echo "  2. 更新 RSS   立即手动刷新一次 rss.xml"
    echo "  3. 卸载 RSS   删除相关文件(需手动删除 1panel 计划任务)"
    echo "  4. 卸载脚本   删除本脚本,可选同时卸载 RSS"
    echo ""
    echo -e "${YELLOW}【生成的文件】${RESET}(均位于网站根目录)"
    echo "  generate_rss.php   RSS 生成脚本"
    echo "  rss.xml            RSS 订阅文件(自动生成)"
    echo ""
    echo -e "${YELLOW}【1panel 计划任务】${RESET}"
    echo "  在 1panel 面板新建计划任务,类型选「Shell 脚本」,"
    echo "  内容填写:"
    echo ""
    echo -e "    ${GREEN}bash $(realpath "$0") update${RESET}"
    echo ""
    echo -e "${YELLOW}【手动触发更新】${RESET}(浏览器访问)"
    echo "  ${SITE_URL}/generate_rss.php?key=${RSS_SECRET}"
    echo ""
    echo -e "${YELLOW}【RSS 订阅地址】${RESET}"
    echo "  ${SITE_URL}/rss.xml"
    echo ""
}

# ============================================================
# 主菜单
# ============================================================
show_menu() {
    echo ""
    echo -e "${CYAN}=============================${RESET}"
    echo -e "${CYAN}        xBlog RSS 订阅       ${RESET}"
    echo -e "${CYAN}=============================${RESET}"
    echo "  1. 安装 RSS"
    echo "  2. 更新 RSS"
    echo "  3. 卸载 RSS"
    echo "  4. 卸载脚本"
    echo "  5. 使用说明"
    echo -e "${CYAN}=============================${RESET}"
    echo -n "请选择操作 [1-5],按 q 退出:"
}

# ============================================================
# 入口
# ============================================================

# 支持直接传参,方便 1panel 计划任务非交互式调用
# 用法:bash /root/rss.sh update
if [ "$1" = "update" ]; then
    do_update
    exit $?
fi

while true; do
    show_menu
    read -r choice
    case "$choice" in
        1) do_install ;;
        2) do_update ;;
        3) do_uninstall_rss ;;
        4) do_uninstall_script ;;
        5) do_help ;;
        q|Q) echo ""; info "已退出。"; exit 0 ;;
        *) warn "无效输入,请输入 1-5 或 q。" ;;
    esac
    echo ""
    read -rp "按 Enter 返回菜单..." _
done

2. 保存文件

Ctrl + X → 确认保存按 Y → 确认文件名按 Enter

3. 赋予执行权限并运行脚本

chmod +x /root/rss.sh && bash /root/rss.sh

💡 脚本使用说明

配置项

配置文件位于 rss.sh 顶部,请根据实际环境修改

配置项 说明 示例值
SITE_DIR 网站根目录路径(宿主机) /opt/1panel/www/sites/xblog/index
SITE_DIR_CONTAINER 网站根目录路径(容器内) /www/sites/xblog/index
SITE_URL 博客域名(末尾不加斜杠) https://xblog.itxgo.com
BLOG_TITLE 博客标题 iTxGo🍃 - xBlog™️
BLOG_DESC 博客描述 PHP + SQLite 简约博客
RSS_LIMIT RSS 收录文章数量 20
RSS_SECRET 手动触发密钥 xblog_rss
BLOG_EMAIL 作者邮箱 itdd@163.com
FILE_OWNER 网站文件所有者 1000:1000
PHP_BIN PHP 执行命令 docker exec php php
SCRIPT_PATH 本脚本自身路径 $(realpath "$0")

菜单功能

选项 功能 说明
1 安装 RSS 自动写入generate_rss.php,并立即生成rss.xml
2 更新 RSS 立即手动刷新一次rss.xml
3 卸载 RSS 删除相关文件(需手动删除 1Panel 计划任务)
4 卸载脚本 删除本脚本rss.sh,可选同时卸载 RSS
5 使用说明 显示当前这份帮助文档

生成的文件

所有文件均位于网站根目录下:

文件 用途
generate_rss.php RSS 订阅生成脚本(核心)
rss.xml RSS 订阅文件(自动生成)

1Panel 计划任务配置

步骤说明

  1. 登录 1Panel 管理面板
  2. 进入「计划任务」功能
  3. 点击「新建计划任务」
  4. 任务类型选择「Shell 脚本
  5. 执行周期按需选择(推荐:每天执行一次)
  6. 脚本内容填写:

bash

bash /root/rss.sh update

🌐 手动触发更新

浏览器访问

https://xblog.itxgo.com/generate_rss.php?key=xblog_rss

⚠️ 安全提示

  • 请将密钥 xblog_rss 替换为您实际设置的 RSS_SECRET
  • 建议使用复杂密钥,防止未授权访问

返回结果示例

成功时:

✅ rss.xml 已生成:/www/sites/xblog/index/rss.xml
📄 共收录 20 篇文章

失败时:

❌ 生成失败,请查看 PHP 错误日志。

🔗 RSS 订阅地址

https://xblog.itxgo.com/rss.xml

📌 可将此地址提交至 RSS 阅读器(Feedly、RSSHub Radar 等)订阅博客更新

出处:https://xblog.aigo.hidns.co/article.php?id=60
版权:本文采用 CC BY-NC-SA 4.0 协议,完整转载请注明来源。