如何部署 Paopao-ce

存在的问题

目前,paopao-ce 存在这样几个问题:

  1. 不存在管理面板,得自己根据 API 写管理工具;
  2. 部署时,本人尚未解决搜索业务不能使用的问题(搜索的接口正常但是返回的数据是空的)。

此文档还在完善

截至目前,使用 ZincSearch 尚无法进行搜索和日志两服务,还在研究中。

准备开始

本文适用于小规模生产部署(比如,自己只有一个乞丐小煮鸡,做出来服务只给你自己或者你认识的朋友),MinIO 之类的对象存储统统不用(太高射炮打蚊子了也)……

首先,出于安全起见,先新建一个专门的用户,这个用户是不给管理员权限的,不能把它添加到 sudo 组中:

sudo adduser paopao

安全起见,所有 Paopao-ce 的进程都使用普通权限运行,占用高位端口,然后再通过 Nginx 反代,将其反代到域名下的路由或者子域名中。

为了能后面和 Web 目录互通文件方便,先新建一个 public 目录,并授予 777 权限:

sudo mkdir /home/public/
sudo chmod a+rw /home/public/

然后先安装基本的 lemp。我在这里使用的是 Oneinstack。当然,使用一般的包管理器安装应该也可以。

安装完成后,使用 Oneinstack 提供的 vhost.sh 先新建一个 virtual host,然后配置好 ssl 访问。如果你使用的是 CloudFlare,需要在 CloudFlare 里新建源站证书(在 SSL/TLS -> Origin Server 中),下载下来私钥和证书以后,放在服务器当中你喜欢的目录里面,配置 Nginx 使用该证书和私钥文件:

server {
listen 80;
listen 443 ssl http2;
ssl_certificate /usr/local/etc/cf-ssl/site-prod/site-prod.cer; //证书
ssl_certificate_key /usr/local/etc/cf-ssl/site-prod/site-prod.pri; //私钥
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-E>
ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SH>
ssl_conf_command Options PrioritizeChaCha;
ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_buffer_size 2k;
add_header Strict-Transport-Security max-age=15768000;
...

注意:

  1. 如果这里存在 ssl_pinning 相关选项,应该将其移除;
  2. 如果你在子域名下运行 Paopao-ce,且一级域名签发的证书或子域名 wildcard 证书和子域名签发的证书不一致或不能通用,需要将 CloudFlare 的 ssl 安全级别由 Full(strict) 降级为 Full。这将对你这个域名下的所有网站的安全性造成影响,请小心

验证

在生产前,请确保 Nginx 关闭了 directory listing 或类似功能。在网站静态文件目录(我这里是 /data/wwwroot/<sitename>)为空的情况下,直接访问该域名的目录,此时应该报一个 403 错误。如果是此情况,则继续按照此文档进行操作。

安装依赖系统

如下,先使用包管理器安装。如果你运行有 Oneinstack,务必不要让它自己装数据库:

sudo apt install golang nodejs npm mariadb-server

然后需要安装 zincsearch。如下:

wget https://github.com/zincsearch/zincsearch/releases/download/v0.4.7/zincsearch_0.4.7_Linux_x86_64.tar.gz
tar -zxf zincsearch_0.4.7_Linux_x86_64.tar.gz
sudo mkdir -p /usr/local/zinc/
sudo mkdir -p /data/zinc/
sudo cp zincsearch /usr/local/zinc/
sudo chown -R paopao /data/zinc/
sudo chmod a+x /usr/local/zinc/zincsearch

写一个 systemd 文件,管理 zincsearch 启动:

[Unit]
Description=Zinc data search service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=paopao
ExecStart=/usr/local/zinc/zincsearch
Environment="ZINC_DATA_PATH=/data/zinc/"
[Install]
WantedBy=multi-user.target

现在需要首次启动 zincsearch 并创建用户,如下:

ZINC_FIRST_ADMIN_USER=admin ZINC_FIRST_ADMIN_PASSWORD=Complexpass#123 ZINC_DATA_PATH=/data/zinc/ /usr/local/zinc/zincsearch

为安全起见,创建一个单独的用户,配置一个单独的权限组,把用户放在权限组里面,给它单独给 index。

设置完后开始处理 MariaDB。

之所以这里不建议使用 MySQL 的原因在于。MariaDB 在 secure_installation 时,可以方便地配置使用 unix socket 来鉴权,我一般的习惯是先使用 sudo 进去创建常规权限的用户,再给他们配置权限。这样在维护时直接使用 sudo mariadb 进数据库,方便又安全。

先使用 sudo mariadb_secure_installation 走完 secure_installation 流程之后,使用 sudo 直接登录进去,开始处理用户和数据库:

create database paopao;
create user 'paopao'@'localhost';
alter user 'paopao'@'localhost' identified by aohw3yfuawhgfou3h4fgou34f;
grant all privileges on paopao.* to 'paopao'@'localhost';

安全起见,只能给这个创建的普通用户这一个数据库的权限。

值得注意的是,如果创建数据库用户时不指定 @localhost,则应该在 config.yaml 配置中写出数据库的地址和端口(比如127.0.0.1:3306),而不是使用 0.0.0.0 来进行连接。从安全层面上而言,0.0.0.0 的设定会使得任意的 IPv4 地址都能与数据库连接,将会导致安全问题。

验证

检查所有新增的系统服务:

ls /etc/systemd/system
dbus-org.freedesktop.timesync1.service php-fpm.service
getty.target.wants sockets.target.wants
multi-user.target.wants sshd.service
mysql.service sysinit.target.wants
network-online.target.wants timers.target.wants
nginx.service zinc.service

目前应该至少有 mysql、nginx、zinc 这几个服务。把它们全部设置为开机自启,并启动:

sudo systemctl enable mysql nginx zinc
sudo systemctl start mysql nginx zinc

安装 Paopao-ce

首先切换进 paopao 用户。在创建该用户时建议也不要给它赋予用户密码,需要使用时,使用 su 命令切换过去:

sudo su - paopao

在家目录中,直接使用 git 下载软件代码:

git clone https://github.com/rocboss/paopao-ce

然后将 scripts 中提供的 paopao-mysql.sql 文件导入 MariaDB 中。

由于等会儿要使用 web 目录下的东西,所以也不要直接下载提供的二进制。升级时,使用二进制文件替换即可。

后端服务配置文件

在配置文件中,要着重关注以下几个方面:

  • 根节点 App 中是由此 web 应用制作的转制桌面客户端的相关配置。
  • 根节点 Server 中是后端服务 API 的配置。
  • 根节点 Features 中是功能集,使用其中的不同功能集,就启动整个服务的不同部分,也可以自己新建功能集或者调整其中内容。
  • 根节点 WebServer 中是后端服务 API 运行的 IP 地址和端口信息。

新建和配置功能集

在小规模部署中,我们需要开以下几个服务。模仿之前功能集的格式,新建一个功能集填入服务名字就行:

  • Admin – 后台管理 API 接口(尚在制作中);
  • Web – 网页 API 服务接口(注意这里不是网页前端,这个服务的前后端是分离的);
  • Zinc – 使用 Zinc 进行搜索的接口(目前,网站和它的对接似乎有点问题);
  • LocalOSS – 本地静态文件存储;
  • MySQL – 数据库服务;
  • Base – 基础服务;
  • LoggerZinc – 使用 Zinc 写入操作日志的接口(目前,网站和它的对接似乎有点问题);
  • LoggerFile – 使用本地文件写操作日志(如果 LoggerZinc 不能使用,可以先使用这个);
  • Migration – 数据迁移服务(从日志情况看,目前似乎暂时无法使用);
  • Redis – 内存缓存数据库,如果启用此项,需要在开始的位置安装 redis,并。
  • Friendship – “好友”相关 API 接口。没有的话,首页上的“好友”功能无法使用。
  • 如果要打开手机验证码功能,需要打开 SmsJuhe 并配置其配置文件节点;如果要打开支付功能,需要打开 AliPay 并配置其配置文件节点。

然后需要改配置里面的几个位置:

  • 根节点 LoggerZinc
    • 需要修改 Host 为你部署的 Zinc 地址和端口;
    • 需要修改 Index 为你分配的 index 名称;
    • 需要修改 User 为你指定的 Zinc 用户;
    • 需要修改 Password 为你指定 Zinc 用户的密码;
    • 假如你用 IP 地址运行 Zinc,需要修改 Secure 为 False;假如你用域名运行 Zinc 服务并且开启了 SSL,需要修改为 True。
  • 根节点 Zinc
    • 和上面类似,但是把你的 Index 指定为数据用的 index 名称
  • 根节点 Database
    • TablePrefix 需要你记住,假如你使用同一个数据库存放好多个 Paopao-ce 实例的数据的话,应该修改这个表前缀,使数据不会冲突。
  • 根节点 MySQL
    • 更改用户名、密码、主机名、数据库名。注意,如果配置文件中的 MySQL 地址是 0.0.0.0:3306,则服务需要使用用户 ‘paopao’@’localhost’ 进行连接。如果这里指定了地址,比如 127.0.0.1:3306,则应该使用 ‘paopao’ 进行连接。

关于 LocalOSS、主站 API 服务和 Nginx 的特别说明

这个位置关乎你的网站能不能正常地加载图片、视频等静态资源。

LocalOSS 的路由是写死了一部分在 paopao-ce 的核心代码里的。无论如何,假如你不做反向代理的话,路由里都会存在一个 /oss/。它的代码大概是这样

package localoss
import (
"path/filepath"
"github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/s/v1"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/sirupsen/logrus"
)
// RouteLocalOSS register LocalOSS route if needed
func RouteLocalOSS(e *gin.Engine) {
savePath, err := filepath.Abs(conf.LocalOSSSetting.SavePath)
if err != nil {
logrus.Fatalf("get localOSS save path err: %v", err)
}
e.Static("/oss", savePath)
logrus.Infof("register LocalOSS route in /oss on save path: %s", savePath)
}
// RouteLocaloss register LocalOSS route if needed
func RouteLocaloss(e *gin.Engine) {
api.RegisterUserServant(e, newUserSrv(), newUserBinding(), newUserRender())
}

还有这样

...
else if cfg.If("LocalOSS") {
    if !LocalOSSSetting.Secure {
        uri = "http://"
    }
    return uri + LocalOSSSetting.Domain + "/oss/" + LocalOSSSetting.Bucket + "/"
}
    return uri + AliOSSSetting.Domain + "/"
}

看到了吗?

也就是说,当 API 接口生成路由的时候,他会按照上面的那个代码生成路由;在 API 的返回里返回图片的网址的时候,他则会按照下面的这个代码,使用字符串拼接的方式生成网址。不管怎么样,网址里面是有一个 oss 在里面的。这个 oss 是分别写死在路由生成和 API 返回的代码里面的。

你需要修改 LocalOSS 配置文件节点,不然容易出现最后在调试器看到文件变成从 127.0.0.1:8080 出来,加载失败的情况。注意,你要修改的配置节点是 LocalOSS,而不是 LocalossServer:

  • 如果 SavePath 所指的路径在目录中不存在,使用普通用户权限创建之。注意,这个路径是相对于 paopao-ce 可执行文件的路径;
  • 如果你主站使用 SSL,将 Secure 改为 True;
  • Bucket 的名字可以随便起。假如你有多个 paopao 实例,修改此值应可避免媒体文件冲突;
  • 现在假设你的主站域名是 t.example.net,如果你使用反向代理把 API 服务反代到了 https://t.example.net/api/,那么 API 服务的端点是 /api/v1/ 。这时候就分三种情况:
    • 如果你想把 LocalOSS 下挂在 /api/oss/ 下,将这里改成 t.example.net/api;
    • 如果你想把 LocalOSS 下挂在 /static/ ,或者什么别的名字,比如 img、cdn、……下,随你喜欢,将这里改成 t.example.net/<你的名字>
    • 如果你想把 LocalOSS 下挂在别的域名,比如 cdn.example.net 下,就将这里改成 cdn.example.net

另外,假如你需要把 LocalOSS 下挂在别的域名,则应该在 Nginx 中新起一个 vhost,然后按照 LocalossServer(注意不是 LocalOSS)的端口号,在 Nginx 中进行反代。

接下来,我们来考虑 Nginx。

这里配置反代大概应该这样写,我问了下 ChatGPT,大概是这样的

location /api/ {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://127.0.0.1:8008/;
    location ~ ^\/api\/oss\/(.*)$ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:8008/oss/$1;
    }
}

127.0.0.1:8008 是主站 API 的地址。这个规则用来把 API 服务挂载在 /api/ 这个子目录下。

如果 location /api/ 这里末尾是带有斜杠的,那么在里面这个 proxy_pass 的网址末尾也应该有斜杠,否则会报 404 错误。

在这个 /api/ 的 location block 中,又套了一个 location block。location 后面的波浪线,表示这条规则进入正则表达式模式。这里分三种情况:

  • 如果你想把 LocalOSS 下挂在 /api/oss/ 下,规则就是 ^\/api\/oss\/(.*)$
  • 如果你想把 LocalOSS 下挂在 /static/ ,或者什么别的名字,比如 img、cdn、……下,随你喜欢,将这里改成 ^\/<你的名字>\/<你的名字>\/(.*)$,比如就改成 ^\/static\/(.*)$
  • 如果你想把 LocalOSS 下挂在别的域名,比如 cdn.example.net 下,则直接新起一个 vhost,在配置文件里面这样写:
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:8008/oss/;
    }

即可。

生成前端文件

在项目目录中切进 web 目录,然后编辑 .env 文件:

  • 如果你的 API 被反代到主站中的目录,比如 /api/,请将 VITE_HOST填写成对应的目录名字,比如就填成 api
  • 要开启全站通知,将 VITE_ENABLE_ANOUNCEMENT 设为 true。
  • 要开启支付功能,将 VITE_ENABLE_WALLET 设为 true。
  • 修改 VITE_DEFAULT_TWEET_IMAGE_404,变成你自己 LocalOSS 里面的。
  • 剩下的配置基本都带有注释,自己改一下就行。

然后修改 package.json,修改里面的 build 命令,改成 node --max_old_space_size=4096 node_modules/vite/bin/vite.js build,总之就是允许 Node 打包时占用更多的内存,不然会 OOM。(emmm……)

然后在 web 目录运行 yarn build

最后,通过 rsync 把 web/dist 下的文件同步给 nginx 的网站目录就行。

可以修改一下最后生成的那个 manifest.json,在那里可以改改网站名字之类的。

服务配置

写一个 systemd 文件,这样就可以通过 systemctl 控制 paopao-ce 的行为(日志的话,要么在 Zinc 里看,要么在日志文件里看)。

从终端直接启动的时候,我是用这个命令:

GIN_MODE=release GOMAXPROCS=4 ./paopao-ce-build --no-default-features --features prod

写成配置文件就这样:

[Unit]
Description=Paopao-ce
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
User=paopao
WorkingDirectory=/home/paopao/paopao-ce
Environment="GIN_MODE=release" "GOMAXPROCS=4"
ExecStart=/home/paopao/paopao-ce/paopao-ce --no-default-features --features prod

[Install]
WantedBy=multi-user.target

最后一句话

值得一提的是,这个泡泡服务存在一种离谱的机控,如果用户没有填手机号,就强制不允许你发表图片和帖子。这时候只需要改下数据库,输入一个空的电话号在里面,你就能愉快地发帖了。