维护页面的变通方法——这能做到吗?

我的更新过程中出现了一个错误

api_key = "a quick brown fox"
fetch("https://api.example.com/data", headers: { 'Authorization' => api_key })

请帮我修复它。

嘿!

我不是在回答你的问题,只是出于好奇。你为什么觉得需要维护页面?设置起来似乎挺麻烦,而且收益甚微。

我的实例每个月停机更新大概5分钟,基本上就这样了。

每当我进行重大更改,例如安装插件时,有时需要花费20分钟。

考虑到这些操作并非每周甚至每月都要进行,这倒不是什么大问题。但对于新访客来说,看到那个显示“某些功能无法正常工作”的丑陋页面,并不会留下良好的第一印象。即使是那些不知道网站会停机的老访客,也可能因此陷入“恐慌模式”,以为整个社区被关闭了或发生了其他严重事故,其中一些人甚至会立刻发邮件问我到底发生了什么。

我认为,无论是新访客还是老访客,告知他们当前正在发生的事情,都是一个贴心的细节。

如果Claude提出的这种方案只需花费几分钟,且之后无需再频繁调整,我相信这绝对是一笔值得投入的时间。

我有一个解决方案,但现在没时间发帖。稍后回复。

如果你迁移到双容器安装,只需几秒钟。

如果你真的愿意,甚至可以在夜间安排切换,趁你和/或你的主要用户群睡觉时进行。

你不需要维护页面。

我使用 双容器构建 并置于 Cloudflare CDN 之后。双容器模式最大限度地减少了停机时间,我的两个主要生产论坛在重建期间的离线时间最多仅为 30 秒。我喜欢使用维护页面,因为默认的网络服务器停机页面既难看,也无法提示用户预计离线多久。

例如,对于位于 your-domain.com 的网站,我只需设置一个指向 *yourdomain.com/* 的 Cloudflare Workers 路由。

步骤 1:创建 Worker 页面

你可以在 Cloudflare 的“Workers & Pages”设置页面中配置维护页面——点击“创建应用”按钮:

然后使用 Hello World 模板:

接着给它起个名字并点击“部署”:

然后进入该维护页面的概览页,点击“编辑代码”:

并将以下代码粘贴到 worker.js 代码窗口中(替换为你的域名以及你想要的任何消息,编辑文本、文字颜色、背景等):

export default {
  async fetch(request, env, ctx) {
    try {
      // 从你的服务器获取原始请求
      const response = await fetch(request);

      // 如果你的网站正在切换容器,它会抛出 502、521 或 530 错误
      if (response.status === 502 || response.status === 521 || response.status === 530) {
        return returnCustomErrorPage();
      }

      // 如果一切正常,返回正常的论坛流量
      return response;
    } catch (e) {
      // 如果服务器完全无法访问
      return returnCustomErrorPage();
    }
  }
};

function returnCustomErrorPage() {
  const html = `
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>系统刷新 - YOUR-SITE.com</title>
      <style>
        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; text-align: center; padding: 50px; color: #333; background-color: #f9f9f9; }
        h1 { font-size: 2.5em; margin-bottom: 0.5em; color: #9400D3; }
        p { font-size: 1.2em; line-height: 1.5; }
        .container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
      </style>
    </head>
    <body>
      <div class="container">
        <h1>短暂的更新窗口</h1>
        <p><strong>YOUR-SITE</strong> 正在进行短暂的 30 秒系统刷新。</p>
        <p>喝口水稍作休息——一旦我们重新上线,此页面将自动刷新。</p>
      </div>
      <script>
        // 每 10 秒自动检查网站是否恢复
        setInterval(function() {
          window.location.reload();
        }, 10000);
      </script>
    </body>
    </html>
  `;

  return new Response(html, {
    status: 503, // 503 对 SEO 最友好,这样 Google 不会因停机而惩罚你
    headers: {
      "Content-Type": "text/html;charset=UTF-8",
      "Retry-After": "30"
    }
  });
}

它应该看起来像这样。点击部署以保存:

步骤 2:添加路由

现在前往 Cloudflare Worker 路由页面,点击“添加路由”按钮以调出新的路由模态框,填写域名路由并选择你刚刚创建的 Worker 页面,然后点击保存:

步骤 3:运行更新或重建

现在,当你 SSH 进入服务器并运行系统更新或进行重建时,当实际发生停机时,将显示此页面,而不是网站停机或错误页面(我设置的是 30 秒,因为这是我的网站的最大停机时间)。

我倾向于在维护时添加和删除我的 Worker 路由页面,但有些人喜欢一直保留它(我保留页面的实际代码,只是添加和删除路由)。

我想就是这样了。

可选:使用脚本和计划运行更新

我还使用服务器上的 Unix Shell 脚本来运行特定的双容器更新,我是这样创建的:

cat << 'EOF' > /root/update-web.sh
#!/bin/bash
cd /var/discourse
echo "➡️ 正在拉取最新的 Discourse Docker 脚本..."
git pull

echo "➡️ 在后台引导新的 web 容器(约需 8 分钟)..."
./launcher bootstrap web_only

if [ $? -eq 0 ]; then
    echo "✅ 引导成功!正在切换容器..."
    ./launcher destroy web_only && ./launcher start web_only
    echo "🚀 完成!网站已更新,几乎零停机。"
else
    echo "❌ 引导失败!中止切换以保持当前网站在线。"
fi
EOF

chmod +x /root/update-web.sh

然后在服务器的 root 提示符下使用命令 ./update-web.sh 运行它。

你可以将其自动化为特定时间,例如当地时间的周日凌晨 1 点。对我来说,当地凌晨 1:00UTC 上午 8:00(你可以通过在 SSH 登录时在命令提示符下输入 date 来确定服务器的 UTC 日期)。

所以:

运行此命令以打开服务器的任务调度器:

crontab -e

(如果它要求你选择编辑器,按你喜欢的编辑器,1 代表 nano,可能最简单)

我滚动到注释底部并粘贴此内容:

0 8 * * 0 /root/update-web.sh >> /var/log/discourse-update.log 2>&1

(这意味着在每分钟 0、小时 8(UTC)、每个周日早上运行 /root/update-web.sh 文件)

然后保存并退出(如果你使用的是 nano):

  1. Ctrl + O 保存。
  2. Enter 确认。
  3. Ctrl + X 退出。

然后我可以在周日早上醒来时运行 cat /var/log/discourse-update.log 以检查它是否正常运行。如果你确实使用了 cron 任务调度器,那么你将需要保留 Worker 页面路由。

我想这就是全部了。如果你有问题请告诉我 lol。这比我描述的简单多了 lol。:grin: