前言

半个月前因为某平台服务器打折,本着“有羊毛不薅是混蛋”的原则购入了一台2核4G的 vps 服务器,之后顺理成章地开始部署各种服务,但由于完全是运维小白,频频陷入容器管理和 Nginx 反向代理配置的诸多问题中,尝试在 V2ex 和 nodeseek 论坛上面寻找更优的方案去做应用部署和管理,最终选择拥抱 Coolify !

同时为了优化我的博客使用体验,决定引入开源评论系统 remark42,在各种尝试之后找到最方便可行的配置方案,所以想分享给大家,方便其他人少踩坑。

Coolify

这里简单介绍一下 Coolify :一个超强大的开源 PaaS 平台,支持轻松部署各类数据库和应用,例如 Supabase/n8n/umami/appwrite 等。并可以一键配置 SSL 证书和反向代理。

coolify.png

官网自称是 Heroku/fly.io/Netlify 的完美平替方案。(前提是你得有一台 vps 服务器

安装步骤非常简单,只需要在服务器执行如下命令,并按照提示操作即可。

curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

简单来看,Coolify 由如下四个部分组成:

  • User Interface: 用户界面,用于配置 Coolify 参数及资源。
  • API: Coolify的后端部分,用于与远程资源、数据库、Docker引擎等进行通信。
  • HTTP/TCP Proxy: 动态配置Traefik实例以生成反向代理配置,生成SSL证书并将所有传入流量路由到正确的容器。
  • SSL certificates are handled by Let’s Encrypt: Coolify 自动生成和存储SSL证书。

基础配置

直接输入 vps 服务器 ip 地址,并加上端口 8000 即可访问。第一个注册用户是 Coolify 实例的 admin 用户,它将可以查看/更改所有部署的资源、访问系统范围的配置、一键更新过程等。其他用户都不会访问系统范围的配置,只能看到他们团队的资源。

如果需要使用域名访问,在完成 DNS 解析之后,可以在左侧的 Settings 中配置域名即可(Coolify会设置反向代理)

如果你使用的是 Cloudflare 的话,建议开启代理,因为 Coolify 会自动把 443 端口请求转发到 8000 端口。

coolify-domain.png

服务搭建

通过 Coolify 支持一键快捷部署应用、数据库、服务。

如下图所示,支持从 git 公共仓库和私有仓库代码部署及 Docker 部署。

service-list.png

同时点击即可创建包括 n8n/appwrite/umami/supabase 等应用。

Remark42 评论系统

之前建站的时候就考虑过接入评论系统,但是由于没有足够多的时间去折腾,所以一直搁置。

最近忙完插件开发的工作之后就开始着手优化博客系统,因为之前在浏览 pseudoyu 的博客时,发现他使用的评论系统主题挺好看,同时该有的功能一样不差,于是考虑接入该评论系统。

首先这里简单介绍一下 Remark42,它是一个轻量且强大的开源评论系统,支持多种 OAuth 包括 Google/Facebook/Github/X 等第三方账号登录。同时使用单文件存储,无需额外配置数据库,方便数据导出和备份。

安装部署

在阅读的其他博客时发现他们使用了 docker-compose 一键构建的方式,但是仍需要花心思和时间去配置 SSL 证书和反向代理,教程复杂且繁琐容易出错,所以这里推荐像我一样使用 Coolify 部署。仅需数分钟即可完成部署上线。

首先第一步需要为 remark42 绑定域名,这里我假设已经把 remark42.demo.com 解析到我的 vps 服务器上。并且我的博客也部署在同一台服务器上面,域名为 blog.demo.com

这里强烈建议评论系统域名与博客域名应使用相同的顶级域名,如果需要跨域名,则需要额外的参数设置,具体见 Configure Instance on a different domain

接下来需要在 coolify 中创建一个新服务,并选择 Docker Compose 部署,之后根据如下的 docker-compose 文件配置即可。

version: '2'
services:
  remark:
    image: 'ghcr.io/umputun/remark42:latest'
    container_name: remark42
    hostname: remark42
    restart: always
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: '5'

    environment:
      # 这里需要将 REMARK_URL 设置为你配置好的评论系统域名
      - REMARK_URL=https://remark42.demo.com
      # 该密钥用于服务端生成JWT (仅支持字母和数字
      - SECRET=${REMARK_SECRET}
      # 评论系统标识。不设置的话默认为remark(后续在博客中嵌入代码时需要用到该标识
      - SITE=${SITE_NAME}
      - DEBUG=true
      # 开启匿名评论。
      - AUTH_ANON=true
      # 管理员ID。一开始可以不设置,后续可以设置。
      - ADMIN_SHARED_ID=${ADMIN_SHARED_ID}
      # Google 登录配置。
      - AUTH_GOOGLE_CID=${AUTH_GOOGLE_CID}
      - AUTH_GOOGLE_CSEC=${AUTH_GOOGLE_CSEC}
      # Github 登录配置。
      - AUTH_GITHUB_CID=${AUTH_GITHUB_CID}
      - AUTH_GITHUB_CSEC=${AUTH_GITHUB_CSEC}
      # Telegram 登录配置。
      - AUTH_TELEGRAM=true
      # 管理员通知配置。
      - NOTIFY_ADMINS=telegram
      - TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
      - NOTIFY_TELEGRAM_CHAN=${NOTIFY_TELEGRAM_CHAN}
      # 用户通知配置。
      - NOTIFY_USERS=email
      # 邮箱登录配置。
      - AUTH_EMAIL_ENABLE=true
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_PORT=${SMTP_PORT}
      - SMTP_TLS=true
      - SMTP_USERNAME=${SMTP_USERNAME}
      - SMTP_PASSWORD=${SMTP_PASSWORD}
      # 邮箱鉴权地址
      - [email protected]
      # 邮箱通知地址
      - [email protected]
    volumes:
      - ./var:/srv/var
    # 直接使用 Traefik 作为反向代理,coolify 会自动完成 SSL 证书的申请和请求转发。
    labels:
      - "traefik.enable=true"
      # --- 路由器规则 ---
      # 规则:当访问的主机是 remark42.demo.com 时,应用此规则
      - "traefik.http.routers.remark42.rule=Host(`remark42.demo.com`)"
      # 入口点:指定流量从哪个 Traefik 入口点进入 (通常是 https 或 websecure)
      - "traefik.http.routers.remark42.entrypoints=https"
      # TLS/SSL:为这个域名启用 HTTPS,并使用名为'letsencrypt'的证书解析器
      - "traefik.http.routers.remark42.tls.certresolver=letsencrypt"

      # --- 服务规则 ---
      # 告诉 Traefik,remark42 容器内部的服务监听在 8080 端口
      - "traefik.http.services.remark42.loadbalancer.server.port=8080"

这里参数很多,但是第一次填写时只需要填入 REMARK_URLSECRET 即可。其他都是可选的,可以后续再配置。

部署验证

在服务部署完成之后,可以通过访问 https://remark42.demo.com/web 来验证是否部署成功。如果出现如下页面,则说明部署成功。

remark42-web.png

如果无法访问该页面,请检查服务是否正常运行及日志中有无报错。

博客接入

以我目前使用的 hugo 博客为例,首先在 layouts/partials/comments.html 中添加如下代码。

<div class="comments">
    <div class="title">
      <span>Comments</span>
      <span class="counter"><span class="remark42__counter" data-url="{{ .Permalink }}"></span></span>
    </div>
    <div id="remark42">
    </div>
  </div>
  
  <script>
    // 动态获取当前主题状态的函数
    function getCurrentTheme() {
      // 检查body是否有dark类
      if (document.body.classList.contains('dark')) {
        return 'dark';
      } else {
        return 'light';
      }
    }

    var remark_config = {
      host: 'https://remark42.demo.com',
      site_id: 'remark',
      components: ['embed', 'counter'],
      max_shown_comments: 20,
      simple_view: true,
      theme: getCurrentTheme(), // 动态获取主题
    }
  </script>
  
  <script>
      (function () {
        const remark42 = window.REMARK42
        if (remark42) {
          remark42.destroy()
          remark42.createInstance(remark_config)
        } else {
          for (const component of remark_config.components) {
            var d = document, s = d.createElement('script');
            s.src = `${remark_config.host}/web/${component}.mjs`;
            s.type = 'module';
            s.defer = true;
            // prevent the <script> from loading mutiple times by InstantClick
            s.setAttribute('data-no-instant', '')
            d.head.appendChild(s);
          }
        }
      })();
  </script>

  <!-- 监听主题切换并同步更新Remark42主题 -->
  <script>
    function setupThemeSync() {
      if (window.REMARK42 && window.REMARK42.changeTheme) {
        const themeToggle = document.getElementById('theme-toggle');
        if (themeToggle) {
          const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
              if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                const newTheme = getCurrentTheme();
                window.REMARK42.changeTheme(newTheme);
              }
            });
          });
          
          observer.observe(document.body, {
            attributes: true,
            attributeFilter: ['class']
          });
        }
      } else {
        setTimeout(setupThemeSync, 500);
      }
    }
    
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', setupThemeSync);
    } else {
      setupThemeSync();
    }
  </script>

只需要根据你部署的 remark42 的域名和 site_id 修改对应的参数即可。

之后查看博客主题下面的 theme/PaperMod/layouts/_default/single.html 文件是否存在 comments 部分,如果有的话开启即可,如果没有的话,则需要手动添加上。

管理员配置

在登录 remark42 评论系统之后,可以点击用户头像,并复制右侧的用户 id。

admin-settings.png

将该 id 填入 ADMIN_SHARED_ID 中,并重启服务。

可以看到评论区出现的 setting 选择,可以选择删除和拉黑评论。

admin-primary.png

通知推送

首先通知类型分为两种,管理员通知和用户通知,具体的区别可以看官网的文档说明,简单理解就是:

  • 管理员通知:所有新评论都发送给管理员(例如发邮箱或 Telegram 频道)。
  • 用户通知:允许用户订阅他们自己评论的回复通知,但需要手动开启。

目前前者支持 Slack, email, 和 Telegram 推送管理员通知。后者仅支持 email 推送用户通知。

管理通知配置

这里以 Telegram 为例,首先需要填写的多个参数。

NOTIFY_ADMINS=telegram
NOTIFY_TELEGRAM_CHAN=-100**********
NOTIFY_TELEGRAM_TOKEN=62*****253:AA***********************************

首先 NOTIFY_TELEGRAM_TOKEN 是 Telegram 机器人的 token,可以通过 @BotFather 获取后获取(具体步骤请参考这篇教程

之后需要创建一个 channel 并获取 channel 的 ID,创建的方式如下在 chat 中点击右上角的编辑,选择 New Channel

create-channel.png

之后需要登录 网页版 Telegram ,并选择 channel 中,复制以 -xxxx 开头的 channel ID 填入 NOTIFY_TELEGRAM_CHAN 中。(必须带上负号

get-channel-id.png

配置完成后重启服务,发送一条评论可以在 channel 里面收到通知。

telegram-notification.png

用户订阅通知配置

目前仅支持 email 推送用户通知,配置方式如下:

  1. 首先配置邮箱 STMP 服务:
  • SMTP_HOST:SMTP 服务器地址
  • SMTP_PORT:SMTP 服务器端口
  • SMTP_TLS:是否启用 TLS
  • SMTP_USERNAME:SMTP 用户名
  • SMTP_PASSWORD:SMTP 密码
  1. 配置邮箱通知:
  • NOTIFY_USERS:通知用户的方式,我选择了了 email, 即邮件通知,则需要配置下文的 SMTP
  • NOTIFY_EMAIL_FROM:邮箱通知的发送地址

Github/X/Google 登录配置

这里推荐参考 官方文档 进行配置。

踩坑点

注意如果博客中使用的标题或者文字渲染出了一个 <div id="remark42"></div> 标签,会导入前端 embed 组件嵌入失败,从而无法正常显示。

参考链接