联合部署 Mastodon 与 Synapse

  1. 1. 准备工作
  2. 2. 正式开始
    1. 2.1. 安装 Docker 环境
    2. 2.2. 准备目录
  3. 3. 搭建 Mastodon
    1. 3.1. PostgreSQL 数据库
    2. 3.2. elasticsearch 全文搜索
    3. 3.3. Mastodon 本体
    4. 3.4. 配置反向代理
  4. 4. 创建 OAuth 应用
  5. 5. 搭建 Matrix Synapse
    1. 5.1. Synapse
      1. 5.1.1. Synapse 本体
      2. 5.1.2. PostgreSQL 数据库
      3. 5.1.3. Redis 数据库
    2. 5.2. Matrix 媒体代理
      1. 5.2.1. media-repo 本体
      2. 5.2.2. PostgreSQL 媒体数据库
      3. 5.2.3. Redis 媒体缓存数据库
    3. 5.3. 周边组件
      1. 5.3.1. element-web
      2. 5.3.2. Synapse Admin
    4. 5.4. 配置反向代理
  6. 6. 参考资料
    1. 6.1. 文档
    2. 6.2. 项目

Mastodon 基于 ActivityPub 协议提供去中心化的联邦宇宙微博服务,Synapse 基于 Matrix 协议提供去中心化的即时通信服务,通过 Mastodon 的 OAuth 2.0 机制让 Synapse 可以共享 Mastodon 的账户信息,进而实现共享域名和单点登录集成,是不是非常优雅呢。

准备工作

  1. 一台服务器,用于架设相关的服务。
    为了便于管理、升级和迁移等维护工作的进行,我是用的 Docker 架设服务容器。请确认您的服务器使用的是 KVM / Xen 等虚拟化技术,OpenVZ 技术由于内核版本过于上古,只有 OpenVZ 7 以上版本的虚拟机(目前似乎不好找到在售的厂商),系统内核才可以支持 Docker 的运行。
    根据目标服务的人群数量确定配置,不要太低(像1G内存这种基本上是带不起来的),但如果用户群体不大的话也无需太高。
    另外,请记得关注资源存储与硬盘空间管理,没有外接存储的尽可能选择硬盘较大的服务器,以便存储接收到的资源文件。当连接上联邦宇宙后,外站的图片可能也会在本地出现对应的缓存;且日志表会增长得非常快,如果有可能的话可以考虑使用定时脚本清理。
  2. 搜索引擎。
    配置过程中会出现大量新的概念和未知的文档,并有可能出现各种稀奇古怪的报错,所以及时收集这些信息、进行分析并使用搜索引擎查找对应的解决方案,也就成了一种重要又方便的习惯。
  3. 耐心。
    这些内容是我研究了一个多星期才确定的最终部署方案。其中很多的内容,网上的教程或是文档要不是过时的,就是没有中文版的,甚至也不乏根本没有文档、纯粹从 StackOverflow 的讨论串中翻出来的实践总结。我能做的就是整理获得的资料,如果有出现任何的疏漏或是错误,欢迎各位在评论区随时指出,我看到后也会第一时间进行跟进修复。
    出于对于每一位读者的帮助与尊重,相关的配置文件我会附在文章中。如果能为您的配置工作带来帮助,那么将是我莫大的荣幸。

正式开始

安装 Docker 环境

  1. 安装 curl (以 Debian 系的 apt 为例)

    1
    2
    apt update
    apt install curl
  2. 安装 Docker 服务

    1
    bash <(curl -L -s https://get.docker.com)
  3. 安装 docker-composer (请记得调整版本号

    1
    2
    3
    curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

准备目录

我由于是使用 root 账户进行创建,站点名为 eihei ,所以将目录分别建立在 /root/eihei/mastodon 和 /root/eihei/matrix 下。可以先仅部署这些目录,后续的随着服务的部署再依次创建。

我最终的文件结构树是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── autoscripts
│   └── backup
├── eihei
│   ├── mastodon
│   │   ├── elasticsearch
│   │   ├── postgres
│   │   ├── public
│   │   └── redis
│   └── matrix
│   ├── element-web
│   ├── media-repo
│   │   ├── data
│   │   ├── db
│   │   └── redis
│   └── synapse
│   ├── data
│   ├── db
│   └── redis
├── netdata
└── portainer

清楚的目录结构对于无论是搭建还是运维,都能提供不小的帮助。
部分目录需要特殊的权限才能运行(例如 elasticsearch 在默认权限下会无限重启,这边偷懒直接使用了 777 权限这种),会在之后的配置过程中会一一阐明。

搭建 Mastodon

由于要使用 Mastodon 作为核心,我们的搭建工作先从 Mastodon 开始。

PostgreSQL 数据库

在搭建 Mastodon 之前,我为数据库指定了额外的环境变量以取代默认的用户和密码。因而新建了一个 .env.pgsql 文件,并在其中输入以下内容(请注意替换成您需要的内容):

1
2
3
POSTGRES_PASSWORD=<Databse_Password>
POSTGRES_USER=<Databse_User>
POSTGRES_DB=<Databse_Name>

在 docker-compose.yml 的 db 段中引入它:

1
2
3
4
  db:
restart: always
+ env_file: .env.pgsql
image: postgres:9.6-alpine

这样当初次启动数据库时,就能自动创建指定的数据库,并赋予指定的用户名以通过指定密码访问的权限了。

elasticsearch 全文搜索

由于服务器的运行资源比较充足,这一次的构建,我为 Mastodon 启用了 elasticsearch (以下简称 es )的支持;并根据官方的提示文档,为 es 进行了中文语境下的优化;并修改了 mastodon 中关于 es 支持的分词代码,构建了站点专用的镜像。

请注意, elasticsearch 在默认目录权限下会因无法读写而无限重启(估计 es 容器里的用户并不是 root),这边偷懒,直接使用如下的指令,赋予了 elasticsearch 目录以 777 权限:

1
chmod 777 ./elasticsearch

之后即可避免 es 无限重启。

先安装 es 的插件。安装插件时,我们需要给 es 以外网权限,因而在 docker-compose.yml 的 es 段 networks 中增加一项 external_network ,安装完成后注释掉,重新 docker-compose up -d 自动重构容器即可。

参考代码如下:

1
2
3
# 在 /root/eihei/mastodon 下执行
docker-compose exec es elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.10/elasticsearch-analysis-ik-6.8.10.zip
docker-compose exec es elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-stconvert/releases/download/v6.8.10/elasticsearch-analysis-stconvert-6.8.10.zip

其中,安装 elasticsearch-analysis-ik 时会有一个允许该插件访问分词器的确认,输入 y 。两个插件都安装完成后,可以重启容器。

如果安装过程中出现版本不匹配的报错,请检查参考代码中 6.8.10 是否为您的 es 版本号(截至截稿时, mastodon 官方的 docker-compose.yml 中使用的是 es 6.8.10 ,请注意版本匹配)

Mastodon 本体

之后是修改 mastodon 中 es 分词器的相关内容,让它调用新安装的中文分词器,而非默认的空格分词。可以参照官方文档,也可以参照 我们站的改动

请不要从默认的 main branch 拉取代码!

main branch 虽然是默认分枝,但其实这个严格来讲应该算是 dev branch,拉取这个分枝的未完成代码很可能会导致您的站点陷入不健康运行状态 (unhealthy) ,并可能因此导致一些奇奇妙妙的问题。为了安全,建议您从 master 拉取代码进行修改。另外,您也可以从形如 stable-* 的稳定备份分枝拉取对应版本的代码,但是在截稿的时候,最新的 3.3 发布版似乎没有完成版本迭代,因而这个中版本暂时还没有稳定的分枝。

其他的内容基本可以参照 Mastodon搭建小记 ,只是这次配置完成后现在已经可以直接初始化数据库并创建管理员用户了。在正式启动之前,记得手动写出环境配置文件,并添加 es 相关的兼容项。

.env.production
1
2
3
+ES_ENABLED=true
+ES_HOST=es
+ES_PORT=9200

另外,关于 wasabi s3 存储的配置也需要额外修改一下,S3_ENDPOINT 推荐改成带有区域的完整端点路径, S3_REGION 也需要调整为实际内容存在的区域。

一切就绪后,使用 docker-compose up -d 来启动服务。没有意外的话,应该在几分钟后即可成功进入站点首页了。

Mastodon 有自带的安全心跳检测机制,通过向检测使用的端口发送数据来获取健康状态,因而可以在 docker ps 时看到健康运行 (healthy) 或是不健康 (unhealthy) 的情况。请注意如果出现 unhealthy ,您应当注意日志问题及相关的报错信息,不健康的站点运行环境可能会导致难以预料的结果。

附上一份我们的 Mastodon 配置文件,希望能为您的配置提供帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Generated with mastodon:setup on 2021-03-16 15:14:43 UTC

LOCAL_DOMAIN=eihei.net
SINGLE_USER_MODE=false
SECRET_KEY_BASE=<Generated_SECRET_KEY_BASE>
OTP_SECRET=<Generated_OTP_SECRET>
VAPID_PRIVATE_KEY=<Generated_VAPID_PRIVATE_KEY>
VAPID_PUBLIC_KEY=<Generated_VAPID_PUBLIC_KEY>
DB_HOST=db
DB_PORT=5432
DB_NAME=<Databse_Name>
DB_USER=<Databse_User>
DB_PASS=<Databse_Password>
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
S3_ENABLED=true
S3_PROTOCOL=https
S3_REGION=eu-central-1
S3_HOSTNAME=s3.wasabisys.com
S3_ENDPOINT=https://s3.eu-central-1.wasabisys.com/
S3_BUCKET=files.eihei.net
AWS_ACCESS_KEY_ID=<Your_S3_Access_ID>
AWS_SECRET_ACCESS_KEY=<Your_S3_Access_Secret_Key>
S3_ALIAS_HOST=files.eihei.net
SMTP_SERVER=smtp.sendgrid.net
SMTP_PORT=587
SMTP_LOGIN=apikey
SMTP_PASSWORD=<SendGrid API key>
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_FROM_ADDRESS='Mastodon <[email protected]>'
ES_ENABLED=true
ES_HOST=es
ES_PORT=9200

配置反向代理

当 Mastodon 建立完成后,我们还需要构建反向代理文件,以让服务正式可以运行。此处使用了官方的 nginx 反向代理进行配置,此处顺带附上文件,请记得根据您的实际情况进行调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

upstream backend {
server 127.0.0.1:3000 fail_timeout=0;
}

upstream streaming {
server 127.0.0.1:4000 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
listen 80;
listen [::]:80;
server_name example.com;
root /home/mastodon/live/public;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

# Uncomment these lines once you acquire a certificate:
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

keepalive_timeout 70;
sendfile on;
client_max_body_size 80m;

root /home/mastodon/live/public;

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

add_header Strict-Transport-Security "max-age=31536000";

location / {
try_files $uri @proxy;
}

location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Strict-Transport-Security "max-age=31536000";
try_files $uri @proxy;
}

location /sw.js {
add_header Cache-Control "public, max-age=0";
add_header Strict-Transport-Security "max-age=31536000";
try_files $uri @proxy;
}

location @proxy {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";
proxy_pass_header Server;

proxy_pass http://backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
add_header Strict-Transport-Security "max-age=31536000";

tcp_nodelay on;
}

location /api/v1/streaming {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";

proxy_pass http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

tcp_nodelay on;
}

error_page 500 501 502 503 504 /500.html;
}

创建 OAuth 应用

为了能建立 Mastodon 和 Matrix 的账号通用,经过许多天的文档研究后,我放弃了使用 LDAP 创建身份系统及连接认证的方案(因为主流平台的界面实在是太丑了),经过多方资料查询后发现 Synapse 已经原生支持 OAuth 2.0 身份认证系统,且 Mastodon 也有对应的 OAuth 2.0 应用创建及管理机制,并且具有使用 API 来获取用户信息的接口,于是瞬间就不犹豫了,直接开始配置!

先去 站点的 首选项 - 开发 (https://您的Mastodon站点实例/settings/applications) 中 创建新应用应用名称 设置为方便辨识的即可,应用的 权限范围 只需要勾选 read:accounts (请取消掉红色的 read write follow 前面的钩子),应用的 跳转URL 设置为 https://您的Synapse站点实例/_synapse/client/oidc/callback 。例如本站设置为 https://eihei.net/_synapse/client/oidc/callback ,用于授权访问的请求回调。

创建完成后会回到应用列表,它应该会出现在应用列表中。单击它进入应用的详细信息,记录下页面顶部的 应用 ID应用密钥 两条记录,这是用来连接 OAuth 2.0 认证系统的关键,记得千万不要泄露。

搭建 Matrix Synapse

这一步可以说是整个流程中最复杂的内容了。由于相关的技术资料并不充足,很多相关的项目还都处于初期开发状态;且由于 Matrix 概念本身相对较新,了解的用户(尤其是中文的用户)并不多,初期的道路也就都只能靠自己来摸打滚爬。但好在参与开发的大佬并不在少数,因而即使资料缺失,相关的技术却都能被投入开发,因而只需要仔细收集文档中的细节,并辅以相关代码的阅读,多少也能搓出一些实用产品的雏形。例如,Synapse 本身已经支持了 OAuth 2.0 方式对接身份认证系统(此处以 Mastodon 为例),只需要收集到对应 API 的端点信息,即可构建出足以用于完成认证操作的完整工作流。

本来我们是打算使用 matrix-docker-ansible-deploy 来构建服务器系统的,但是这个部署工具有一个缺陷:作为附加传入参数的 SSO 认证模板中使用了一些变量,但是这些变量会被 ansible 认为是用于部署时候的参数配置,因而会导致过不去预检查。所以就只能使用最原始却也是最安全的方式——直接部署了。

但是文档一样还是甚少,而且总是遇到一些稀奇古怪的情况,诸如配置文件注释把内容分割开了报错、数据库初始化的参数报错、数据库初始化时候尝试访问报错、容器回调地址控制出错等等等甚至完全可以说是无厘头的内容。不过总也有办法——搜索引擎。经过将近一整个星期的折磨,它总算是能工作上了,可喜可贺可喜可贺呀。

所以长话短说,先送上我们实例的配置文件(docker-compose.yml),之后会分每一个模块,分别介绍对应配置的原因,和该模块独有的配置文件。

  1. docker-compose.yml (关键数据已脱敏处理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
version: "3.4"
services:
synapse:
hostname: matrix
image: matrixdotorg/synapse:latest
restart: always
container_name: eihei_matrix_server
depends_on:
- db
- redis
- media_repo
ports:
- "127.0.0.1:10801:8008"
volumes:
- ./synapse/data:/data
networks:
- synapse_network
- external_network
healthcheck:
test: ["CMD-SHELL", "curl -s localhost:8008/health || exit 1"]

db:
image: postgres:9.6-alpine
restart: always
container_name: eihei_matrix_db
volumes:
- ./synapse/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: <Databse_User>
POSTGRES_PASSWORD: <Databse_Pass>
POSTGRES_DB: <Databse_Name>
POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
networks:
- synapse_network
healthcheck:
test: ["CMD", "pg_isready", "-U", "<Databse_User>"]

redis:
image: redis:6.0-alpine
restart: always
container_name: eihei_matrix_redis
volumes:
- ./synapse/redis:/data
networks:
- synapse_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]

# External media API for S3 storage
media_repo:
hostname: media_repo
image: turt2live/matrix-media-repo:latest
restart: always
container_name: eihei_matrix_media_repo
depends_on:
- media_db
ports:
- "127.0.0.1:10802:8000"
volumes:
- ./media-repo/data:/data
networks:
- media_network
- external_network

media_db:
image: postgres:13.2-alpine
restart: always
container_name: eihei_matrix_media_db
volumes:
- ./media-repo/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: <Databse_User>
POSTGRES_PASSWORD: <Databse_Pass>
POSTGRES_DB: <Databse_Name>
networks:
- media_network
healthcheck:
test: ["CMD", "pg_isready", "-U", "<Databse_User>"]

media_redis:
image: redis:6.0-alpine
restart: always
container_name: eihei_matrix_media_redis
volumes:
- ./media-repo/redis:/data
networks:
- media_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]

element-web:
image: vectorim/element-web:latest
restart: always
container_name: eihei_matrix_element_web
depends_on:
- synapse
ports:
- "127.0.0.1:10804:80"
volumes:
- ./element-web/config.json:/app/config.im.eihei.net.json:ro
networks:
- external_network

synapse_admin:
image: nyaone/synapse-admin:latest
restart: always
container_name: eihei_matrix_admin
depends_on:
- synapse
ports:
- "127.0.0.1:10803:80"
networks:
- external_network

networks:
synapse_network:
internal: true
media_network:
internal: true
external_network:

先别慌,这里的内容其实可以分成三大块:Synapse本体 + Matrix 媒体代理 + 周边组件。(本次我们没有部署 ma1sd 身份认证服务器,因为我们觉得支持 3PID 的 Synapse 本身就已经足够强大了;而且我们的用户全都是使用 SSO 作为认证方式的,只要 Mastodon 端数据没有丢失,那么用户的信息就是安全的。秉承着这样的思想,我们仅仅是为鸡肋的媒体管理选择了一个新的模块以便对接 S3 存储(我们用的是 wasabi ),其他的内容全部交给 Synapse 管理。

题外话:为什么没有使用 dendrite

虽然 dendrite 是使用 go 编写的下一代 Matrix 服务器,性能更加强劲(配置也更加复杂,还有提供分布式部署的方案),但因为 dendrite 尚处于初步开发阶段,目前的功能覆盖率严重不足,并且兼容性也还不佳;大型项目的迭代需要一个缓慢的过程,按照 Synapse 现在的开发完成度来看,可能两三年内都不会被 dendrite 取代。

Synapse

Synapse 本体承担着存储几乎所有数据的职责。为了最大化其运行性能,我们使用了 redis 作为缓存数据库,启用了 PostgreSQL 为其提供内容存储及数据库支持,而非默认的 SQLite 数据库(在高负载下的表现比较糟糕)。为了保证运行安全性,我们使用 docker 为 synapse 单独设置了一个隔离内网 synapse_network ,除了 synapse 本身的端口通过 nginx 的反向代理部署在外(监听127.0.0.1:10801端口),其他的包括 redis 和 PostgreSQL 全部都仅运行在内部网络上。因而我们的 Synapse 配置下有三个目录: data 、 db 和 redis 。其中 data 用于存放 Synapse 运行相关的数据文件,例如配置内容、或是用于 Matrix 协议通信的密钥等; db 目录中存放的是 PostgreSQL 的数据, redis 中存放的是 redis 的数据。因而在 docker-compose.yml 中,Synapse 占据三项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
services:
synapse:
hostname: matrix
image: matrixdotorg/synapse:latest
restart: always
container_name: eihei_matrix_server
depends_on:
- db
- redis
- media_repo
ports:
- "127.0.0.1:10801:8008"
volumes:
- ./synapse/data:/data
networks:
- synapse_network
- external_network
healthcheck:
test: ["CMD-SHELL", "curl -s localhost:8008/health || exit 1"]

db:
image: postgres:9.6-alpine
restart: always
container_name: eihei_matrix_db
volumes:
- ./synapse/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: <Databse User>
POSTGRES_PASSWORD: <Databse Pass>
POSTGRES_DB: <Databse Name>
POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
networks:
- synapse_network
healthcheck:
test: ["CMD", "pg_isready", "-U", "<Databse User>"]

redis:
image: redis:6.0-alpine
restart: always
container_name: eihei_matrix_redis
volumes:
- ./synapse/redis:/data
networks:
- synapse_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]

networks:
synapse_network:
internal: true
external_network:

Synapse 本体

关于 Synapse 的配置,首先按照 Docker Hub 上的官方镜像里的说明 生成一个:

1
2
3
4
5
6
# 记得调整您站点对应的目录位置和域名!
docker run -it --rm \
-v /root/eihei/matrix/synapse/data:/data \
-e SYNAPSE_SERVER_NAME=eihei.net \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
  • -v /root/eihei/matrix/synapse/data:/data 表示将 /root/eihei/matrix/synapse/data 映射入容器的 /data 中,从而能实现数据的持久化(而不是一重构容器就直接损失所有数据);生成的相关配置文件和签名密钥也能被自动带出,进而为配置和部署工作带来方便。请注意根据您的实际情况调整对应的目录位置!docker run 不能使用相对路径(只有 docker-compose 可以),所以请使用绝对路径运行您的代码。
  • -e SYNAPSE_SERVER_NAME=eihei.net 表示通过容器的环境变量将域名设置为 eihei.net ,请根据您实际的域名来配置此处的内容。
  • -e SYNAPSE_REPORT_STATS=no 表示不上报使用数据。

能看到这样的输出:

1
2
3
4
5
6
Creating log config /data/eihei.net.log.config
/usr/local/lib/python3.8/site-packages/twisted/conch/ssh/common.py:14: CryptographyDeprecationWarning: int_from_bytes is deprecated, use int.from_bytes instead
from cryptography.utils import int_from_bytes, int_to_bytes
Generating config file /data/homeserver.yaml
Generating signing key file /data/eihei.net.signing.key
A config file has been generated in '/data/homeserver.yaml' for server name 'eihei.net'. Please review this file and customise it to your needs.

这样就能在 ./synapse/data 目录下看到三个文件:

  • homeserver.yaml
  • eihei.net.log.config
  • eihei.net.signing.key

其中 homeserver.yaml 是 Synapse 的主配置文件, .log.config 结尾的是 Synapse 的日志配置文件, .signing.key 结尾的是 Synapse 用于 Matrix 协议的签名密钥。 eihei.net 是运行 docker run 指令时您输入的站点名,如果您输入的是 example.com ,那么生成的文件就变成了 example.com.log.config 和 example.com.signing.key 。

着重需要关注的,还是 homeserver.yaml 配置文件。

配置文件中的内容非常详细,基本一边读一边就能配置起来了。如果认为有必要的话,您可以尝试完成阅读。但由于考虑到快速部署及可用性迁移的需求,此处还是附上本站的配置文件,并逐一讲解其中每一项的内容:

homeserver.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Configuration file for Synapse.

server_name: "eihei.net"
pid_file: /data/homeserver.pid
web_client_location: https://im.eihei.net/
public_baseurl: https://eihei.net/
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
acme:
enabled: false
port: 80
bind_addresses: ['::', '0.0.0.0']
reprovision_threshold: 30
database:
name: psycopg2
args:
user: <Databse User>
password: <Databse Pass>
database: <Databse Name>
host: db
cp_min: 5
cp_max: 10
log_config: "/data/eihei.net.log.config"
enable_media_repo: false
url_preview_enabled: true
url_preview_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '192.0.0.0/24'
- '169.254.0.0/16'
- '192.88.99.0/24'
- '198.18.0.0/15'
- '192.0.2.0/24'
- '198.51.100.0/24'
- '203.0.113.0/24'
- '224.0.0.0/4'
- '::1/128'
- 'fe80::/10'
- 'fc00::/7'
- '2001:db8::/32'
- 'ff00::/8'
- 'fec0::/10'
url_preview_url_blacklist:
- scheme: 'http'
- netloc: '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'
max_spider_size: 5M
url_preview_accept_language:
- zh
- en
enable_registration: false
allow_guest_access: false
auto_join_rooms:
- "#public:eihei.net"
report_stats: false
macaroon_secret_key: <Macaroon Secret Key>
form_secret: <Form Secret>
signing_key_path: "/data/eihei.net.signing.key"
key_refresh_interval: 1d
trusted_key_servers:
- server_name: "matrix.org"
oidc_providers:
- idp_id: mastodon
idp_name: Mastodon
idp_brand: "net.eihei.mastodon" # optional: styling hint for clients
discover: false
issuer: "https://eihei.net/@admin"
client_id: <OAuth Client ID>
client_secret: <OAuth Client Secret>
authorization_endpoint: "https://eihei.net/oauth/authorize"
token_endpoint: "https://eihei.net/oauth/token"
userinfo_endpoint: "https://eihei.net/api/v1/accounts/verify_credentials"
scopes: ["read:accounts"]
user_mapping_provider:
config:
subject_claim: "id"
localpart_template: "{{ user.username }}"
display_name_template: "{{ user.display_name }}"
sso:
client_whitelist:
- https://im.eihei.net/
password_config:
enabled: false
ui_auth:
session_timeout: "30s"
email:
smtp_host: "smtp.sendgrid.net"
smtp_port: 587
smtp_user: "apikey"
smtp_pass: <SendGrid API key>
require_transport_security: true
notif_from: "Synapse <[email protected]>"
client_base_url: "https://im.eihei.net/"
user_directory:
prefer_local_users: true
redis:
enabled: true
host: redis
port: 6379
# vim:ft=yaml
  • server_name 用户的域名。请注意此处需要设置为您想要显示在 @username:example.com 中的这一串 example.com ,而并不一定是您 Synapse 服务器的域名——您完全可以将服务器架设在 matrix.example.com 上,然后通过之后会提及到的 well-known 设置来引导服务器/客户端来指向您需要用作服务的域名和端口。
  • pid_file 服务器运行的 PID (Process ID,进程唯一标识符) 文件。使用默认提供的 /data/homeserver.pid 即可。
  • web_client_location 网页客户端的地址。最后请以/结尾,以保证截断域名,避免恶意站点的攻击;
  • public_baseurl 公共的基础URL。客户端可以使用这个 URL 来访问到 _matrix 路由及以下的内容。本站由于使用的是根域名直接服务,所以就和主域名一样啦。最后同样请以/结尾,以保证截断域名。
  • listeners Synapse 监听的端口。由于我们使用容器部署,保留默认即可。如果有必要的话,可以配置多项。
    • port 监听的端口。
    • tls 是否启用 TLS 证书加密。
    • type 协议类型。一般都是 http
    • x_forwarded 使用 X-Forwarded-For 头来标记被反向代理之前的IP。
    • resources 该端口监听的资源。
      • names 资源的名称。 client 表示客户端与服务器的连接, federation 表示服务器与服务器间的联合
      • compress 是否压缩数据。
  • limit_remote_rooms 限制远程房间。此处可以为远程房间设置复杂度检查规则(其实我不是太懂),所以保持了默认的空对象(关闭)状态。
  • retention 消息保留时间规则。本站选择了默认的状态,即留空配置项,默认永久保留消息。以后可能会有改动,届时再参阅配置文件就好。
  • acme 使用 acme 申请 Lets’Encrypt 的 TLS 证书。因为会套一个反向代理,所以这一块就保留了默认的设置,即禁用状态。
    • enabled 是否启用。默认为 false ,即不启用。
    • port 监听的端口。如果启用的话,它会以 HTTP 文件验证的方式来申请证书,所以需要一个监听端口,默认为80。请注意,如果您使用 acme 申请证书,请一定保证您穿透出去的外部端口(即 acme 通过 HTTP 协议连接到服务器使用的)的端口为80!
    • bind_addresses 绑定的地址。默认绑定所有的 IPv4/v6 地址。
    • reprovision_threshold 证书过期多少天前就申请新的证书。
  • caches 全局缓存配置。因为不会配置,所以就保留了默认的状态。
    • per_cache_factors 看不太懂。
  • database 数据库。默认使用了 SQLite 3 本地文件数据库,这种方式最轻量,但缺点是它不支持并发、受限于磁盘性能,当负载较高时会有糟糕的表现。考虑到我们服务器那块曾经炸得不瞑目的硬盘…还是连接 PostgreSQL 数据库吧。
    • name 数据库提供者。请记得一定要使用 psycopg2 以表示连接 PostgreSQL 数据库,这不是在开玩笑。
    • args 连接数据使用的参数列表。
      • user 数据库用户名
      • password 数据库密码
      • database 数据库名
      • host 数据库主机名(hostname),请记得和 docker-compose.yml 中设置的一致。例如使用我们的这种配置时,它应当为 db
      • cp_min 最小的异步连接池数量(异步操作用于提高数据库的操作效率,并发处理由数据库进行实现),保留默认应该就可以。
      • cp_min 最大的异步连接池数量,保留默认应该就可以。
  • log_config 日志文件的配置。由于之前自动生成了一个配置文件,我看了看似乎没有什么特别值得注意的地方,所以就没有在意。保持默认应该就可以。
  • enable_media_repo 启用媒体文件管理。因为 Synapse 的媒体文件管理系统比较简陋,没法很好地处理与 S3 的连接存储问题(参见 synapse#7140),所以我们外接了一个存储处理系统 matrix-media-repo 用于将文件上传至 S3 存储。
  • url_preview_enabled 启用 URL 预览功能。
  • url_preview_ip_range_blacklist URL 预览功能的 IP 黑名单,每一项都是一个 IP/CIDR 格式的地址。包含的都是本地地址,以避免通过 URL 预览功能窥探到服务器内部的数据。
  • url_preview_url_blacklist URL 预览功能的域名黑名单。
    • scheme 协议头。如 http 指屏蔽所有的 HTTP 开头的链接, https 不受影响。
    • netloc 网络地址。可以是域名,也可以是 IP 地址。例如提供的样例中提供的就是 IP ,即屏蔽所有的 IP 直连的站点。
  • max_spider_size 最大的网络爬虫大小。防止爬虫过大导致引起网络卡顿,或是服务器资源被恶意占用等。
  • url_preview_accept_language URL 预览功能支持的语言。比起默认的英文,额外设置了中文。
  • enable_registration 开放注册。由于是使用 SSO 授权登录,所有的账户系统均要求以 Mastodon 为准,因而该项被设置为 false ,以避免未授权的注册请求。
  • account_validity 账号验证。默认关闭,保持空配置关闭即可。
  • registration_shared_secret 注册通用机密。使用该机密可以在站点关闭的情况下执行注册请求,会在生成配置文件时自动生成一大串,因为不希望用到,所以出于安全起见直接删除。
  • allow_guest_access 允许访客访问。出于安全因素,拒绝。
  • auto_join_rooms 自动加入房间。当用户注册时,自动将用户加入到特定的房间中。默认情况下,房间不存在时会自动创建。
  • report_stats 是否上传匿名使用数据。出于安全因素考虑,不上传。
  • macaroon_secret_key 用来为访问令牌签名的机密,生成配置时会自动生成,无需额外关注。
  • form_secret 用于计算表单 HMAC 的机密,生成配置时会自动生成,无需额外关注。
  • signing_key_path 用于联合的签名密钥文件路径,使用默认配置的即可。
  • key_refresh_interval 本机服务器签名密钥的有效周期。默认即可。
  • trusted_key_servers 可信第三方密钥服务器。每一项为一个服务器,默认设置了 matrix.org 作为可信密钥服务器。您可以参阅配置文件中对应的内容。本站将在第二站点设置完成后,将可信密钥服务器仅限制于两站点间的互联。
  • oidc_providers OIDC 认证的提供者。这是此次配置的重头戏,但其实(按照这个教程)配置起来并不困难。
    • idp_id 身份提供者(ID Provider)的 ID 。这里标注为 mastodon ,方便辨识,也不容易冲突。
    • idp_name 身份提供者的名字。这里标注为 Mastodon ,方便辨识。
    • idp_brand 用于标识身份提供者。不是太懂,写成逆序域名的方式就好。
    • discover 启用 OIDC 的端点发现机制来自动发现端点。由于 Mastodon 的身份认证端点在 API 处,需要手动指定,所以这里设置为 false 。
    • issuer OIDC 的发布者,当发现机制启用时,用于验证 OIDC 的提供者端点。虽然因为没有启用 discover 功能,可以先不管,但本站的配置很显然是错误的。
    • client_id 之前在 Mastodon 上创建应用时收集到的 客户端 ID 。
    • client_secret 之前在 Mastodon 上创建应用时收集到的 客户端机密。
    • authorization_endpoint 授权端点。参照 Mastodon OAuth 文档,此处设置为 “https://mastodon.example/oauth/authorize"。
    • token_endpoint 令牌端点。参照 Mastodon OAuth 文档,此处设置为 “https://mastodon.example/oauth/token"。
    • userinfo_endpoint 用户信息端点。参照 Mastodon Accounts API 文档,此处设置为 https://mastodon.example/api/v1/accounts/verify_credentials
    • scopes 申请的资源域。遵循权限最小化原则,仅需要 “read:accounts” 。
    • user_mapping_provider 用户信息映射。将 Mastodon 的用户信息映射为可供 Synapse 使用的用户信息。针对 Mastodon 的部署,直接照搬本站的配置就好。
      • config 用户信息映射配置。
        • subject_claim 用户身份唯一标识符。因为 Mastodon 的 API 中没有 sub 段(只是使用了 Mastodon 附带的身份认证功能),所以此处设置为 “id” 。
        • localpart_template 本地段模板。就是 Matrix 协议中使用的本地用户名,直接使用 Mastodon 的用户名就好。
        • display_name_template 展示名模板。用 Mastodon 的用户昵称吧。
  • sso Synapse 授权其他应用(例如客户端、管理端等)时使用的单点登录相关操作。这里主要是设置白名单。
    • client_whitelist 用户端白名单。当是该列表中的客户端发出请求时,可以直接跳过用户确认授权的界面。这里设置了我们官方在线服务器的地址。请一定记得使用 / 截断域名,以避免形如 example.com.another.domain 这种恶意访问情况出现。
  • password_config 密码登录相关配置。无需密码登录的可以直接设置一个 enabled: false 就好。
    • enabled 是否启用密码登录。那当然是不启用了,都有 SSO 单点登录了,还开放的话不是等着被人打嘛。(猫猫摊手)
  • ui_auth 界面授权认证,即授权其他应用访问 Synapse 的设置。
    • session_timeout 会话有效时间。考虑到本站由于 CloudFlare 的奇妙保护效果,暂时就还是先设置成 30 秒吧。
  • email 邮件发送系统的设置。可以用于发送让用户绑定邮箱使用的邮件。
    • smtp_host SMTP 主机。此处以 SendGrid 为例。
    • smtp_port SMTP 端口。此处以 587 为例。
    • smtp_user SMTP 用户名。此处使用 SendGrid API 的方式认证,那么是 apikey 。
    • smtp_pass SMTP 密码。此处填写申请到的 SendGrid API 。
    • require_transport_security 需要传输安全协定。当然是了。
    • notif_from 发件人。和 Mastodon 那边的设置差来不多吧。
    • client_base_url 客户端的基础地址。设置为网页客户端的地址吧。
  • user_directory 用户目录相关的设置。
    • prefer_local_users 是否更偏向于本地用户。我们选择的为是。
  • redis Redis 数据库参数配置。
    • enabled 启用 Redis 数据库连接。
    • host Redis 数据库的主机名。请注意调整为您 docker-compose.yml 中配置的内容。
    • port Redis 数据库的端口。默认是6379,没有修改就不需要变化。

配置完成后,请记得删除配置文件中的注释行和空行,尽可能把整个配置文件拼在一起,以避免出现设置内容被阻断、进而造成服务器报错,或是设置失效的问题。

PostgreSQL 数据库

由于 Synapse 对于数据库有一定的初始化参数要求(编码设置为 UTF-8, LC_COLLATE 和 LC_CTYPE 设置为 C),因而我们加入了一串初始化参数(从 StackOverflow 还是 Reddit 的一个相关讨论里隐约找到的方案),也就是这个环境变量:

1
2
# Add to PostgreSQL `environment` part
POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"

其他的内容倒都没什么问题,按照规范化的流程进行即可。

Redis 数据库

只要启动了就可以,记得不要监听到外网去,免得被人利用了。

Matrix 媒体代理

matrix-media-repo (以下简称 media-repo) 这个项目是用来解决 Synapse 本身媒体代理能力拉跨问题的,所以直接按照配置文件书写就好。此处附上官方的文档链接

同样,先附上 docker-compose.yml 段中的相关代码,再解释为什么这样配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# External media API for S3 storage

services:
media_repo:
hostname: media_repo
image: turt2live/matrix-media-repo:latest
restart: always
container_name: eihei_matrix_media_repo
depends_on:
- media_db
ports:
- "127.0.0.1:10802:8000"
volumes:
- ./media-repo/data:/data
networks:
- media_network
- external_network

media_db:
image: postgres:13.2-alpine
restart: always
container_name: eihei_matrix_media_db
volumes:
- ./media-repo/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: <Databse_User>
POSTGRES_PASSWORD: <Databse_Pass>
POSTGRES_DB: <Databse_Name>
networks:
- media_network
healthcheck:
test: ["CMD", "pg_isready", "-U", "<Databse_User>"]

media_redis:
image: redis:6.0-alpine
restart: always
container_name: eihei_matrix_media_redis
volumes:
- ./media-repo/redis:/data
networks:
- media_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]

networks:
media_network:
internal: true
external_network:

media-repo 本体

这个 media-repo 直接通过 external_network ,在本地就能连接 synapse 对用户请求进行鉴权。

着重提一下 media-repo 的配置。官方的文档写得全面,但是默认的配置文件却很简短,基本要翻遍了文档才能整理出一份合适的内容。老规矩,贴上配置文件,我们一行一行来解读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
repo:
# Generally the bind address should be local as the media repo should be
# behind a reverse proxy.
bindAddress: '0.0.0.0'
port: 8000
database:
# Currently only PostgreSQL is supported. This is *not* the same as your
# homeserver's database.
postgres: "postgresql://<Databse_User>:<Databse_Pass>@media_db/<Databse_Name>?sslmode=disable"
homeservers:
- name: 'eihei.net'
csApi: "http://synapse:8008"
admins:
- "@candinya:eihei.net"
datastores:
- type: s3
enabled: true
forKinds: ["all"]
opts:
tempPath: "/tmp/mediarepo_s3_upload"
endpoint: s3.eu-central-1.wasabisys.com
accessKeyId: "<Your_S3_Access_ID>"
accessSecret: "<Your_S3_Access_Secret_Key>"
ssl: true
bucketName: "<Your_S3_Bucket_Name>"
region: "eu-central-1"
thumbnails:
maxSourceBytes: 10485760 # 10MB default, 0 to disable
numWorkers: 100
expireAfterDays: 0
sizes:
- width: 32
height: 32
- width: 96
height: 96
- width: 320
height: 240
- width: 640
height: 480
- width: 800
height: 600
dynamicSizing: false
allowAnimated: true
defaultAnimated: false
maxAnimateSizeBytes: 10485760 # 10MB default, 0 to disable
stillFrame: 0.5
types:
- "image/jpeg"
- "image/jpg"
- "image/png"
- "image/gif"
- "image/heif"
- "image/webp"
#- "image/svg+xml" # Be sure to have ImageMagick installed to thumbnail SVG files
urlPreviews:
previewUnsafeCertificates: false
disallowedNetworks:
- "127.0.0.1/8"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "100.64.0.0/10"
- "169.254.0.0/16"
- '::1/128'
- 'fe80::/64'
- 'fc00::/7'
allowedNetworks:
- "0.0.0.0/0"
  • repo media-repo 的主配置段。
    • bindAddress 绑定的监听地址,监听 0.0.0.0 表示接受来自所有 IP 的入站请求。因为我们是使用 Docker 运行,并不知道即将分配到什么本地 IP ,所以不能写 127.0.0.1 ;反正由 Docker 的网关负责网络转发,此处也就不必在意太多。
    • port 监听的端口,需要和 docker-compose.yml 中的保持一致。
  • database media-repo 连接的数据库。目前只支持 PostgreSQL 。请注意这个是单独给 media-repo 使用的数据库,并不是 Synapse 使用的主数据库;出于安全因素考虑,不建议通用。
    • postgres PostgreSQL 的连接串,请注意把格式中对应的的变量改成您在 docker-compose.yml 中的设定值。
  • homeservers 需要代理的 Matrix 服务器。
    • name 用户的域名,也就是 @username:example.com 那一串中的 example.com ,用于请求 host 验证,请注意并不是您的 Synapse 或是该 media-repo 的地址。写错的话处理媒体文件时会直接拒绝。
    • csApi client-server API ,用来处理用户请求鉴权的。 media-repo 可以直接向 “http://synapse:8008" 发送请求数据。请注意这个 synapse 应当和您在 docker-compose.yml 中配置的 Synapse 主机名一致。
  • admins 管理员。一项一个,为完整身份地址串。虽然不是太懂有什么操作需要管理员身份来实现。
  • datastores 数据存储,用于存储目标文件。一般是用来存到 S3 上,但是如果是存在本地使用的话…其实用 Synapse 自带的媒体管理工具就很好哦?
    • type 媒体存储系统的类型。由于我们是使用 S3 存储,选择 s3 。
    • enabled 是否启用。自然是要启用了。
    • forKinds 针对启用的类型。由于为了避免所有的文件都存在本地,使用 all 就好。如果是根据不同的文件分别选择不同的数据集合来存储的方案,可以参照 官网的文档 进行相关的配置。
    • opts 附加选项。
      • tempPath 上传到 s3 前临时存储的目录。可以做持久化也可以不做,这个随意。
      • endpoint 存储端点。格式可以查询您使用的存储文档的相关信息,例如我们这里设置为 s3.eu-central-1.wasabisys.com 。
      • accessKeyId 存储桶访问 ID 。没有什么特别需要注意的。
      • accessSecret 存储桶访问机密。没有什么特别需要注意的。
      • ssl 使用 SSL 连接。 Wasabi 是支持 SSL 的,对于其他的提供商请查询您提供商的相关文档。
      • bucketName 存储桶的名称。有一点需要注意: media-repo 上传文件时不会自带文件前缀,所有的文件都会直接被堆在存储桶的根目录里,因而建议新开一个存储桶而不是沿用已有的,以避免给相关的内容带来混乱,甚至是损坏已有的数据等。
      • region 存储桶所在的地域。
  • thumbnails 缩略图相关配置
    • maxSourceBytes 最大缩略的资源大小,单位是 Byte 。
    • numWorkers 并行缩略工作的数量。
    • expireAfterDays 文件过期时间(天),即多少天后自动删除这些文件。
    • sizes 文件大小,以下每一项表示一个尺寸
      • width
      • height
    • dynamicSizing 是否动态调整大小,即根据客户端的请求,自动裁切缩略图。为了避免资源过度消耗,此处设置为 否 。
    • allowAnimated 是否允许动图。默认设置为 是 。
    • defaultAnimated 是否默认裁切成动图。
    • maxAnimateSizeBytes 最大动图大小,单位为 Byte 。设置为 0 可以禁用。
    • stillFrame 静帧比率。默认设置为 0.5 .
    • types 需要缩略的类型。每一项都代表一种文件格式;如果您需要为 SVG 图片生成缩略图,请确保您安装了 ImageMagick 插件,再清除掉 - “image/svg+xml” 行最前面的 # 注释号。
  • urlPreviews URL 预览媒体。参照之前 Synapse 的配置就好。(不过这里配置了的话,我之前 Synapse 配置的是什么?)
    • previewUnsafeCertificates 为不安全证书的站点也生成预览。为了避免被攻击,就还是算了吧。
    • disallowedNetworks 不生成预览的网络。默认是一些私有 IP 地址。
    • allowedNetworks 允许生成预览的网络。默认是所有 IP 地址。请求会先检查是否被拒绝,如果未在拒绝列表内,再会匹配是否在允许网络中。

基本一遍下来,配置完成即可。

PostgreSQL 媒体数据库

比 Synapse 的好配置,没有稀奇古怪的问题,按照样例修改一下相关的参数就可以。注意一下只需要加入 媒体代理 的网络就好。

Redis 媒体缓存数据库

更方便了,直接用就可以,完全没有问题。同样注意一下只需要加入 媒体代理 的网络就好。

周边组件

这些东西因为全是纯前端的,所以直接启动容器就好。容器只需要外部网络,无需内部的访问方案。当然, element-web 需要稍微多配置一下; synapse-admin 官方版本并不支持 SSO 登录也不支持中文,所以我使用的是我魔改的一个版本。

element-web

Element 的在线版本客户端,可以视为是 Matrix 协议客户端的一个强大实现,配置一下站点默认配置文件就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"default_server_name": "eihei.net",
"brand": "诶嘿 (. ❛ ᴗ ❛.)",
"bug_report_endpoint_url": "mailto:[email protected]",
"showLabsSettings": true,
"piwik": false,
"enable_presence_by_hs_url": {
"https://eihei.net": true
},
"terms_and_conditions_links": [
{
"url": "https://eihei.net/terms",
"text": "使用协议 & 隐私条款"
}
],
"settingDefaults": {
"language": "zh-hans"
}
}

看着情况改就可以; zh-hans 是简体中文,如果有需要,可以改成其他语言。

额外需要关注的是映射到 Docker 容器中的文件名,要是您用于部署该前端客户端的 URL 为 element.example.com ,那么您的配置文件应当映射至容器中的 config.element.example.com.json 文件。同样您可以发现,无论您有多少个客户端,其实只需要这样单独一个实例 + 不同的配置文件名即可解决。

Synapse Admin

直接部署就好,这个是纯前端应用,无需任何后端连接。

需要额外注意的是,默认情况下第一个注册的人也不是 Synapse 服务器的管理员,所以需要手动执行以下的语句(记得将对应的变量都替换掉):

1
docker exec -it <Your_Synapse_DB_Container> psql -U <Your_DB_User> -d <Your_DB_Name> -c "UPDATE users SET admin = 1 WHERE name = '@<Your_UserName>:<Your_SiteAddr>';"

当看到 UPDATE 1 的返回时,说明记录被成功更新,指定的用户已经成为管理员了。

配置反向代理

以上内容全部搭建完成后,我们需要为 Synapse 配置反向代理。参考 官方的配置文件 ,结合先前 Mastodon 的配置内容,我使用了这样的全站配置方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

upstream mastodon_backend {
server 127.0.0.1:3000 fail_timeout=0;
}

upstream mastodon_streaming {
server 127.0.0.1:4000 fail_timeout=0;
}

upstream matrix_backend {
server 127.0.0.1:10801 fail_timeout=0;
}

upstream matrix_media_repo {
server 127.0.0.1:10802 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
listen 80;
#listen [::]:80;
server_name eihei.net;
#root /home/mastodon/live/public;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}

server {
listen 443 ssl http2;
#listen [::]:443 ssl http2;
server_name eihei.net;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

# Uncomment these lines once you acquire a certificate:
ssl_certificate /etc/nginx/ssl/eihei_net/cert.pem;
ssl_certificate_key /etc/nginx/ssl/eihei_net/key.pem;

# Cloudflare Pull-authenticate
ssl_client_certificate /etc/nginx/ssl/cloudflare.crt;
ssl_verify_client on;

keepalive_timeout 70;
sendfile on;
client_max_body_size 80m;

#root /home/mastodon/live/public;

gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

add_header Strict-Transport-Security "max-age=31536000";

## well-known
location /.well-known/matrix/server {
# Allow access from *
add_header Access-Control-Allow-Origin '*';
return 200 '{ "m.server": "eihei.net:443" }';
}

location /.well-known/matrix/client {
# Allow access from *
add_header Access-Control-Allow-Origin '*';
return 200 '{ "m.homeserver": { "base_url": "https://eihei.net" } }';
}

## Mastodon reverse proxy

location / {
try_files $uri @proxy;
}

location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Strict-Transport-Security "max-age=31536000";
try_files $uri @proxy;
}

location /sw.js {
add_header Cache-Control "public, max-age=0";
add_header Strict-Transport-Security "max-age=31536000";
try_files $uri @proxy;
}

location @proxy {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";
proxy_pass_header Server;

proxy_pass http://mastodon_backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
add_header Strict-Transport-Security "max-age=31536000";

tcp_nodelay on;
}

location /api/v1/streaming {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Proxy "";

proxy_pass http://mastodon_streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

tcp_nodelay on;
}

error_page 500 501 502 503 504 /500.html;

## Matrix reverse proxy

## Matrix HomeServer
location ~* ^(\/_matrix|\/_synapse\/client) {
proxy_pass http://matrix_backend;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;

# Nginx by default only allows file uploads up to 1M in size
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
client_max_body_size 100M;
}

# ## Matrix Synapse API
# location ^~ /_synapse/admin {
# proxy_pass http://matrix_backend;
# proxy_set_header X-Forwarded-For $remote_addr;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_set_header Host $host;
# }


## Matrix Media API
location ^~ /_matrix/media {
proxy_read_timeout 60s;

# Make sure this matches your homeserver in media-repo.yaml
# You may have to manually specify it if using delegation or the
# incoming Host doesn't match.
proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://matrix_media_repo; # Point this towards media-repo
}

}

请注意两块 well-known 的内容,/.well-known/matrix/server 中指定的是供其他 Matrix 服务器进行互联通信使用的地址,而 location /.well-known/matrix/client 中标注的则是客户端连接到服务器时查询的地址信息,以便于指导客户端的行为。

由于 Matrix 互联及通信使用的是 HTTPS 协议,所以我们可以使用这样的方案同时兼顾 CDN 和服务。

另外,被注释掉的 ## Matrix Synapse API 段是供 Synapse-Admin 使用的 Synapse Admin API 路由,如果您有对应的需要,您可以删除掉这些内容的注释。

出于安全起见,您当然也可以将 API 放置在其他的路由下。但是请注意,由于 media-repo 对于这些路由没有授权,所以请在复制配置时将 media-repo 下的配置段中的 $host 改成您指定的站点域名;而对于 Synapse Admin API 路由,请务必保留原始域名,以避免 SSO 授权登录时可能出现的找不到会话问题。

而关于 element-web 和 synapse-admin 的反向代理部署,其实很方便,此处就直接丢配置文件了。

synapse-admin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
upstream matrix_synapse_admin_backend {
# the synapse_admin server
server 127.0.0.1:10803;
keepalive 64;
}

server {
# nginx listens to this
listen 443 ssl http2;

# the virtual host name of this
server_name admin.eihei.net;

ssl_certificate /etc/nginx/ssl/eihei_net/cert.pem;
ssl_certificate_key /etc/nginx/ssl/eihei_net/key.pem;

# Cloudflare Pull-authenticate
ssl_client_certificate /etc/nginx/ssl/cloudflare.crt;
ssl_verify_client on;


location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://matrix_synapse_admin_backend;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_set_header Connection "keep-alive";
proxy_store off;
}
}
element-web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
upstream matrix_element_backend {
# the element server
server 127.0.0.1:10804;
keepalive 64;
}

server {
# nginx listens to this
listen 443 ssl http2;

# the virtual host name of this
server_name im.eihei.net;

ssl_certificate /etc/nginx/ssl/eihei_net/cert.pem;
ssl_certificate_key /etc/nginx/ssl/eihei_net/key.pem;

# Cloudflare Pull-authenticate
ssl_client_certificate /etc/nginx/ssl/cloudflare.crt;
ssl_verify_client on;


location / {
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://matrix_element_backend;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_set_header Connection "keep-alive";
proxy_store off;
}
}

如果我没有疏漏什么的话,设置完对应的端口,服务应该就能全都正常启动了。

参考资料

文档

  1. 全文搜索 - Mastodon documentation
  2. Configuring Synapse to authenticate against an OpenID Connect provider - Synapse
  3. SSO Mapping Providers - Synapse
  4. OAuth - Mastodon documentation
  5. accounts - Mastodon documentation
  6. S3 - matrix-media-repo
  7. Using Postgres - Synapse
  8. Using a reverse proxy with Synapse

项目

  1. Matrix Synapse
  2. Matrix server setup using Ansible and Docker
  3. matrix-media-repo
  4. postgres - docker
  5. portainer
  6. Element