在搬瓦工重建FarBox2.0

由于不堪忍受 ucloud 香港机房 2Mbps 的小水管,蹲了几天搬瓦工的粉丝惊喜,蹲到了美西 DC6 机房,2c2g 的特价机。
$99.90 一年的高品质线路,加上这么多年(第一次用好像是 2015 年)都没垮,应该是靠谱的服务商。果断剁手,准备搬家。

由于主机性能还行,先布了个 n8n,可以跑一些自己喜欢的定时任务以及 webhook 节点——主要是因为黑群晖跑 n8n 太卡了,本地网络编辑能用出九十年代上网的顿挫感。最新的 n8n 社区版镜像包含两个内容,一个是 n8n 本体,一个是叫 Traefik 的组件,作为类似 Nginx 的反代。整个 n8n 直接跑官方的 docker-compose 就可以顺利使用。

而对于部署 FarBox(FB) 的困难我是有心理准备的,两年前在 UCloud 做第一次部署就零零碎碎花了一年多时间才顺利运转。为了给下次迁移 FB(如果还有的话)打下基础,赶紧从头记录下来,也给选择 FB 作为静态博客框架的人们留个路标。

主机情况
纯裸 VPS,2 vCPU, 2GB memory, 2.5G 网口,机房在 US-LA,GIA 线路
Ubuntu 22.04
域名托管和 DNS 都在 Cloudflare(CF)

第一个困难很快遇到:Ubuntu 22.04 自带 Python 3.10,pip 版本也不再支持 python 2.7。而当年 FB 的开发者 Hepo 选择了 Python 2.7 作为开发语言,众所周知 Python3 在升级时发生了重大破坏性升级,导致与 Python2 语法不兼容,组件完全无法使用。所以如果要使用官方的安装方法,就一定要造出一个 Python2 环境以便执行官方脚本中的 xserver 小工具。在历经 Pipenv 因为依赖问题安装出错,最后 pipenv 因为版本问题无法造出 Python2 环境等一系列死循环依赖后,重新思考:如果使用 NAS 部署,NAS 上的 python 版本更加多样,作者是如何解决的?
回到作者后续更新的文档 如何部署到 NAS 上 里找到用 docker run 来部署的裸脚本,大概明白了 xserver 脚本的功能。

#!/bin/bash
docker run -d \
 -p 7788:80 -p 443:443 -p 80:80 \
 -v /home/run/$name$/configs:/mt/web/configs \
 -v /data/log/$name$:/mt/web/log \
 -v /data/$name$:/mt/web/data \
 -v /data/$name$_ssdb:/mt/ssdb/data \
 -v /static/$name$:/mt/web/static \
 -v /log/docker:/mt/docker/log \
 hepochen/farbox_bucket:latest

按照部署 NAS 的描述:

FarBox 的运行,需要一些配置文件,也就是最终 Docker 容器内要读取的 /mt/web/configs 路径,这里的内容可以从 https://github.com/hepochen/FarBox/tree/master/farbox_bucket/deploy/run/configs 获得,也可以直接下载 https://doc.farbox.org/_attachments/2021-04-21/configs.zip 解压处理。

下载好 configs 文件,解压到 /home/run/farbox/configs 这个 docker 映射的本地路径(不然会无法正常启动容器)。执行上面的 bash 脚本,即可使用。

按照往常的习惯,装好 Nginx,准备在做反代分离 n8n 和 FB,发现 80 端口与 Traefik 冲突。修改 Traefik 监听,让出 80 端口,在 CF 配好 DNS 和 TXT,FB 即可正常访问。
但是一方面用着 Nginx 和 Traefik 两套代理有些代码洁癖上的膈应,另一方面 80 端口二者只能其一使用,另一套注定要用域名+端口号伴其一生有些落寞。更重要的是,如果用 apt install nginx 装的 Nginx 试图通过反代 proxy_pass 给 Traefik,再跳转 n8n,结果竟然是不成立!猜测是 Traefik 直接监听了底层 socket,并且其主动服务发现直接连接了下游服务。因此 Nginx 与 Traefik 处于并列状态,没有形成路由的前后关系。于是决定二者留下一个,改动另外一个的入口,把所有的下游服务集中起来管理。Nginx 用起来已经非常熟悉但配置上略显陈旧,而 Traefik 一方面看起来很新鲜,另一方面其自带 Let'sencrypt 好像能剩下未来续证书的烦恼——虽然如果使用 Cloudflare CDN 其实已经把它做完了。
于是卸掉了 Nginx,把 80 端口交给 Traefik。Traefik 的官网有简单的配置方法,docker 模式下,修改 docker-compose.yml 配置即可,把 FB 的挂在配置写进去。

  farbox:
    image: hepochen/farbox_bucket:latest
    restart: always
    ports:
      - "7788:80"
      - "127.0.0.1:8443:443"
      - "127.0.0.1:8080:80"
    labels:
      - traefik.enable=true
      - traefik.http.routers.farbox.rule=Host(`test.wellwellsleep.com`)
      - traefik.http.routers.farbox.tls=true
      - traefik.http.routers.farbox.entrypoints=web,websecure
      - traefik.http.routers.farbox.tls.certresolver=mytlschallenge
      - traefik.http.middlewares.farbox.headers.SSLRedirect=true
      - traefik.http.middlewares.farbox.headers.STSSeconds=315360000
      - traefik.http.middlewares.farbox.headers.browserXSSFilter=true
      - traefik.http.middlewares.farbox.headers.contentTypeNosniff=true
      - traefik.http.middlewares.farbox.headers.forceSTSHeader=true
        #      - traefik.http.middlewares.farbox.headers.SSLHost=${DOMAIN_NAME}
      - traefik.http.middlewares.farbox.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.farbox.headers.STSPreload=true
      - traefik.http.routers.farbox.middlewares=farbox@docker
    volumes:
      - /home/run/farbox/configs:/mt/web/configs
      - /data/log/farbox:/mt/web/log
      - /data/farbox:/mt/web/data
      - /data/farbox_ssdb:/mt/ssdb/data
      - /static/farbox:/mt/web/static
      - /log/docker:/mt/docker/log

所有的服务使用 127.0.0.1:[ports] 即可,避免了大量的端口暴露,这一点在之前的反向代理配置中一直没有注意,这次也补上了。

基本的迁移就此结束。接下来要把 farbox_client 接上,数据和模板还原,SES 测试通过,就可以正式上线啦。


2023-10-21 更新

之前还是低估了 FB 的坑。由于 FB 的镜像内实际带有 Nginx,但配置不透明,因此按照上述方式去部署 FB 时,FB 内部的类 SaaS 管理会出现奇奇怪怪的 bug。比如:

  • /admin 管理员界面,如果使用超管私钥登录,无法访问超管页独有的 BucketsDomains 等页面,会提示 404
  • 如果使用普通用户私钥登录,则在使用 绑定域名到 Bucket 时,无法出现需要设置在 DNS 服务商的 TXT 验证值,填入任何值都会提示「no-login」,也就是页面跳转后没带上鉴权信息

反复排查端口和翻查 Traefik 的 配置手册 后,偶然的机会下,发现使用直接 IP 访问加端口号的方式,即不使用 HTTPS 的话,就可以正常使用相关功能。这样一来似乎问题就明确了:FB 的某些页面跳转可能没有兼容 HTTPS 方式,使得 Traefik 在自动全路径 HTTPS 化后,部分链接出现死链或无法继承 session 信息。

最终的 docker-compose.yml 部署文件如下(可以看到注释掉的配置里,即便我尽量控制 Traefik 放弃自动 HTTPS,但实际效果仍然是所有在 Traefik 注册的域名都自动加上了 HTTPS):

version: "3.7"

services:
  traefik:
    image: "traefik"
    restart: always
    command:
      - "--api=true"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--accessLog"
      - "--entrypoints.web.address=:80"
        #- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
        #- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.fb7788.address=:7788"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
      - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8888:8080"
    volumes:
      - traefik_data:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

farbox:
    image: hepochen/farbox_bucket:latest
    restart: always
    ports:
      - "7788:80" # 7788 端口需要公开,用于 farbox_client 的客户端同步
      - "8443:443"
      - "8080:80"
    labels:
      - traefik.enable=true
      - traefik.http.routers.farbox.rule=Host(`test.wellwellsleep.com`) || Host(`tech.wellwellsleep.com`) # 租户域名在这里添加
        #- traefik.http.routers.farbox.tls=true
      - traefik.http.routers.farbox.entrypoints=web,websecure,fb7788
      - traefik.http.routers.farbox.tls.certresolver=mytlschallenge
      - traefik.http.services.farbox.loadbalancer.server.port=80
      - traefik.http.middlewares.farbox.headers.SSLRedirect=true
      - traefik.http.middlewares.farbox.headers.STSSeconds=315360000
      - traefik.http.middlewares.farbox.headers.browserXSSFilter=true
      - traefik.http.middlewares.farbox.headers.contentTypeNosniff=true
      - traefik.http.middlewares.farbox.headers.forceSTSHeader=true
      - traefik.http.middlewares.farbox.headers.SSLHost=${DOMAIN_NAME}
      - traefik.http.middlewares.farbox.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.farbox.headers.STSPreload=true
      - traefik.http.routers.farbox.middlewares=farbox@docker
    volumes:
      - /home/run/farbox/configs:/mt/web/configs
      - /data/log/farbox:/mt/web/log
      - /data/farbox:/mt/web/data
      - /data/farbox_ssdb:/mt/ssdb/data
      - /static/farbox:/mt/web/static
      - /log/docker:/mt/docker/log

volumes:
  traefik_data:
    external: true

当要在 FB 中增加租户的时候,使用 IP+端口号(本例为 8080) 的方式强制使用 HTTP 设置即可。

话说我都忘了如何增加租户,步骤是:
1. 使用超管账户邀请
2. 使用 /__register (两个下划线)路径输入邀请码后设好私钥登录
2. 到租户的管理页绑定 new-domain
3. (照目前的配置)去 docker-compose.yml 里 farbox 的服务部分,增加 Host(new-domain) 的路由规则,

最后想想,其实 CF 的 DNS 做 CDN 的时候已经套了 HTTPS,本质上在主机的网关内完全可以使用 HTTP 访问。只是由于 n8n 在部署后强制要求 HTTPS,所以引入了 Traefik,结果 FB 又不完全兼容……

Comments
Write a Comment
  • wellsleep reply

    测试 ses

  • wellsleep reply

    23333

  • wellsleep reply

    again test

  • wellsleep reply

    traefik sts false test

  • wellsleep reply

    is http ok?

    • wellsleep reply

      @wellsleep actually no

  • wellsleep reply

    new ses test with defined referrer policy

  • back to https+referrer

  • remove 127.0.0.1

  • well reply

    does it work now with same host and referer?

  • sad reply

    it will not work with https link because no referrer and host fields

    • well reply

      @sad 啊?丢评论?!

  • wellsleep reply

    https://github.com/hepochen/FarBox/blob/daeda4f5080467f1ddf4b60424b8562f914756bd/farbox_bucket/server/comments/notification.py#L66

    此函数内对 client_referrer 和 request.referrer 做比较,相同或发现为空则丢弃。

    因此修改 traefik,增加 header 的字段。

    1. http 可看到明确的 host 和 referer 值

    2. https 无法解析 Host 和 referer,增加 X-Forword-Host 疑似解决

    2023-11-07a.png

    2023-11-07b.png

  • wellsleep reply

    https for all traffic

    localhost for service discover

    • wellsleep reply

      @wellsleep success

'