去年开始博主大致确立了一个以 RSS 聚合为主,其它信源随缘查看的资讯订阅流程。一大需求是同步不同客户端的阅读记录,需要一个服务器端运行的订阅器。当时用的是 Tiny Tiny RSS 和它的 fever 插件,结合 tt-rss 安卓客户端和 Reeder 实现。
忙起来之后,碎片信息积攒得多了,有点超载。闲暇时间,也多消耗在读书类的 APP 和微信的公众号、看一看这类身边人分享的信息之中。加之手上服务器多了,这一套东西也无暇维护,不值得投入精力在反反复复的安装和修改配置上。最近这段时间也逐渐把手头的各个网站都迁移到 Docker,到了 tt-rss 这个站,想不清过去在这里魔改过什么,索性整个服务器重装了。
经过这样的崩溃、重组的经历,RSS 这一工具在我的角度的使用场景也明确到了两个字:必读。RSS 满足从关注的人、关注的领域开始发散的强调知识的树形结构,与微信构筑的特有的以人为中心、不刻意的“口耳相传”这样的圈层结构相结合,形成属于我的资讯的主要来源。
进一步梳理得到以下需求:阅读不局限于一个地方,tt-rss 的强大功能反而更不适合,需要小巧精悍、能满足获取必读的信息的阅读器;获得的信息不局限于日常所见,需要穿过检查站,就有不被 spy 和 filter 的需求;并且不希望在运维上花太多精力,程序应易于维护,安装配置有记录,方便迁移和维护。
对应的解决方案:
环境:Ubuntu 18.04 + Docker CE + Docker Compose
这里的关键是如何配置一个支持 HTTPS 的接入的“网关”,我们以 Nginx 作为网站的总入口,Nginx 有配套的 Docker 镜像,只需要拉取镜像,然后加载一些自定义配置即可。
结合 Docker 镜像内部的配置,最后沉淀的目录结构如下:
site-env
├── nginx-config # Nginx 自定义配置
| ├── conf.d # 各个站点的配置文件
| ├── snippets # 代码片段
| ├── ssl # HTTPS 证书相关文件
| └── nginx.conf # 覆盖 Docker 容器初始的配置入口
├── logs # 存放日志文件
└── docker-compose.yml # Docker Compose 配置文件
先在 docker-compose.yml
先配置一个 Nginx 服务:
version: "3.6"
services:
nginx:
image: nginx:1.17-alpine
container_name: docker-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./logs:/var/log/nginx
- ./nginx-config/nginx.conf:/etc/nginx/nginx.conf
- ./nginx-config/conf.d:/etc/nginx/conf.d
- ./nginx-config/snippets:/etc/nginx/snippets
- /etc/nginx/ssl:/ssl
- ./nginx-config/ssl/dhparam.pem:/ssl/dhparam.pem
# - /var/www/websites:/wwwroot
extra_hosts:
- "localhost:127.0.0.1"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off http://localhost/get-health || exit 1"]
interval: 5s
retries: 12
logging:
driver: "json-file"
options:
max-size: "100m"
几个关键:
docker-nginx
,在证书签发的流程会用上。dhparam.pem
,自定义 Diffie-Hellman 密钥交换参数,增强安全性然后还需创建宿主机保存证书的目录:
$ mkdir /etc/nginx
$ mkdir /etc/nginx/ssl
生成 dhparam.pem:
$ cd nginx-conf/ssl/
$ sh generate-dhparam.sh
稍等片刻生成完毕,拉取镜像、启动 Nginx 服务器。
$ docker-compose up -d
这一步的代码已整理到 GitHub 仓库 zgq354/docker-nginx-env,更多细节参考仓库的内容。
我这里的域名的 DNS 绑在 Cloudflare,用 acme.sh 的 DNS 验证方式,签发支持泛域名 HTTPS 证书(假设这里域名为 domain.com)。
$ export CF_Key="***"
$ export CF_Email="[email protected]"
$ acme.sh --issue --dns dns_cf -d domain.com -d *.domain.com
待验证通过即可。
调用 acme.sh 安装证书,把安装后的 reload-cmd 设为重启 Nginx 容器:
acme.sh --installcert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/fullchain.cer \
--reloadcmd "docker restart docker-nginx"
到了这一步,所有的 HTTPS 服务需要的证书已经准备好了,容器里面用 /ssl/example.com.key
和 /ssl/fullchain.cer
与其一一对应。
在继续之前,确认已在 DNS 添加需要的域名解析记录。
结合目录结构,按照 Nginx 的姿势,给出 server 配置,放在 nginx-conf/conf.d/
之下。
关于配置的内容,这里需要额外引入 ssl_certificate
和 ssl_certificate_key
,其它参数在 snippets/ssl-params.conf
,参考:
server {
listen 443 ssl http2;
ssl_certificate /ssl/fullchain.cer;
ssl_certificate_key /ssl/example.com.key;
include snippets/ssl-params.conf;
server_name rss.example.com;
server_tokens off;
access_log /var/log/nginx/wbrss-access.log;
error_log /var/log/nginx/wbrss-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://service.weiborss:3000/;
}
}
其它服务则可以通过在 docker-compose.yml
里面加入新的内容,然后 docker-compose up -d
重新启动。这里我配置了 miniflux, RSSHub, weibo-rss 三个站点,以及它们各自依赖的服务:
docker-compose.yml
version: "3.6"
services:
nginx:
image: nginx:1.17-alpine
container_name: docker-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./logs:/var/log/nginx
- ./nginx-config/nginx.conf:/etc/nginx/nginx.conf
- ./nginx-config/conf.d:/etc/nginx/conf.d
- ./nginx-config/snippets:/etc/nginx/snippets
- /etc/nginx/ssl:/ssl
- ./nginx-config/ssl/dhparam.pem:/ssl/dhparam.pem
# - /var/www/websites:/wwwroot
extra_hosts:
- "localhost:127.0.0.1"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off http://localhost/get-health || exit 1"]
interval: 5s
retries: 12
logging:
driver: "json-file"
options:
max-size: "100m"
miniflux:
image: miniflux/miniflux:latest
expose:
- 8080
depends_on:
- db
environment:
- DATABASE_URL=postgres://miniflux:[email protected]/miniflux?sslmode=disable
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- BASE_URL=https://rss.example.com/
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=adminpassword
db:
image: postgres:latest
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=secret
volumes:
- miniflux-db:/var/lib/postgresql/data
service.rsshub:
image: diygod/rsshub
restart: always
environment:
NODE_ENV: production
CACHE_TYPE: redis
REDIS_URL: 'redis://db.redis:6379/'
PUPPETEER_WS_ENDPOINT: 'ws://service.browserless:3000'
depends_on:
- db.redis
- service.browserless
service.browserless:
image: browserless/chrome
restart: always
service.weiborss:
image: zgq354/weibo-rss
restart: always
db.redis:
image: redis
restart: always
volumes:
- redis-data:/data
volumes:
miniflux-db:
redis-data:
nginx-conf/conf.d/rss.conf
# miniflux
server {
listen 443 ssl http2;
ssl_certificate /ssl/fullchain.cer;
ssl_certificate_key /ssl/example.com.key;
include snippets/ssl-params.conf;
server_name rss.example.com;
server_tokens off;
access_log /var/log/nginx/rss-access.log;
error_log /var/log/nginx/rss-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://miniflux:8080/;
}
}
# rsshub.example.com
server {
listen 443 ssl http2;
ssl_certificate /ssl/fullchain.cer;
ssl_certificate_key /ssl/example.com.key;
include snippets/ssl-params.conf;
server_name rsshub.example.com;
server_tokens off;
access_log /var/log/nginx/rsshub-access.log;
error_log /var/log/nginx/rsshub-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://service.rsshub:1200/;
}
}
# weibo-rss
server {
listen 443 ssl http2;
ssl_certificate /ssl/fullchain.cer;
ssl_certificate_key /ssl/example.com.key;
include snippets/ssl-params.conf;
server_name wbrss.example.com;
server_tokens off;
access_log /var/log/nginx/wbrss-access.log;
error_log /var/log/nginx/wbrss-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://service.weiborss:3000/;
}
}
经过实践,对于我之前所总结出关于资讯订阅的姿势,大的方向是没错的,需要调整的大都是一些细节。RSS Feed 模式是资讯源在数学角度最本质的抽象,但生活是具体的,更多时候需要结合生活的节奏,调整其中的各种实现细节,实现我们想要的效果。
参考: