😀 前言

Matrix 搭建全记录(一)—— 使用 Docker 部署 Dendrite

之前部署的 Dendrite 的坑实在是太多了,开发也停滞,很多的新特性包括 Matrix 2.0 依然没有实现,最后还是决定重新回到 Synapse ……

本文记录了我从头开始部署 Synapse 并且进行一些功能加强(滑动同步,OpenID 登录,中文搜索)的步骤

📙 我的部署哲学

  • 使用 Docker:减少对系统的改动,不受架构限制,便于备份、管理以及平滑迁移
  • 组件最小化:只搭建服务端必要组件,不部署客户端,使用官方网页/客户端即可
  • 非侵入性:尽可能少改动现有服务,不影响其他服务的正常运行

💼 准备工作

  • 一个在 Cloudflare 托管的域名和子域名,需要解析并开启 CDN
    • Homeserver 域名: 比如我的实例是 matrix.mykeyvans.com
    • 根域名: 我的实例是 mykeyvans.com ,作为用户标识的域名
  • 有一个云服务器(最少 1H1G),比如我的实例服务器搭建在本地的 Oracle ARM 上

⚒️ 开始部署

事前准备

生成配置文件

首先,创建并进入容器编排目录,比如 /app/matrix

执行以下命令生成配置

docker run -it --rm \
    -v $(pwd)/synapse:/data \
    -e SYNAPSE_SERVER_NAME=mykeyvans.com \
    -e SYNAPSE_REPORT_STATS=no \
    -e UID=1000 \ # 可选:如果是非 root 环境
    -e GID=1000 \ # 可选:如果是非 root 环境
    ghcr.io/element-hq/synapse:latest generate

准备数据库

为了在之后实现中文搜索,我使用了内置 Zhparser 分词功能的 PostgreSQL 镜像

替换 postgres 用户密码后,先创建 postgres 文件夹,在数据路径下执行

$ docker run -it --rm --name postgres \
	--user "$(id -u):$(id -g)" \
	-e POSTGRES_PASSWORD=<超级密码> \
	-e ALLOW_IP_RANGE=0.0.0.0/0 \
	-v /etc/passwd:/etc/passwd:ro \
	-v $(pwd)/postgres:/var/lib/postgresql/data \
	ghcr.io/abcfy2/zhparser:17

新开一个终端,输入指令进入交互模式

$ docker exec -it postgres /bin/bash

# 以下进入容器中
ac10e16d4511:/$ psql -U postgres 

# 进入 PostgreSQL 交互命令行
psql (17.2 (Debian 17.2-1.pgdg120+1))
Type "help" for help.

# 创建数据库
postgres=# CREATE USER **synapse** WITH PASSWORD '<Synapse 密码>';
CREATE ROLE
postgres=# CREATE DATABASE **synapse** WITH OWNER = **synapse** ENCODING = 'UTF8' LC_COLLATE = 'C' LC_CTYPE = 'C' TEMPLATE = template0;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE **synapse** TO **synapse**;
GRANT
postgres=# \q

ac10e16d4511:/$ exit

执行完毕后,关闭新开的终端,并且在原执行终端里面按 Ctrl+C 结束执行,创建数据库的步骤就成功了

修改配置

使用 PostgreSQL 数据库

修改 synapse/homserver.yaml 中关于数据库的设置 (参考)

database:
  name: psycopg2
  txn_limit: 10000
  args:
    user: **synapse**
    password: <Synapse 密码>
    dbname: synapse
    host: postgres
    port: 5432
    cp_min: 5
    cp_max: 10

使用 Redis 缓存

Redis 是一个高效的缓存数据库,能够显著减少服务端的内存占用,修改 synapse/homserver.yaml 以启用 redis

redis:
  enabled: true
  host: redis
  port: 6379
  dbid: 0

设置 Homeserver 域名

由于我的服务器架设在子域名 homeserver.mykeyvans.com 下,因此需要添加以下设置 (参考)

public_baseurl: https://homeserver.mykeyvans.com/
serve_server_wellknown: true

配置 Nginx 反向代理

生成网页证书

由于我们使用 Cloudflare CDN,我建议使用自签的空证书来保证安全性,防止泄露源站IP

⭐ 如果你决定不使用 CDN,你需要自行配置有效的证书,推荐使用 Caddy 来自动管理

以下指令逐行执行,生成证书部分全部回车并采用默认值即可

# 创建证书目录并切换到文件夹
$ mkdir -p ./nginx/certs
$ cd ./nginx/certs

# 生成自签证书
$ openssl ecparam -genkey -name prime256v1 -out ca.key
$ openssl req -new -x509 -days 7305 -key ca.key -out ca.crt
$ openssl ecparam -genkey -name prime256v1 -out empty.key
$ openssl req -new -sha256 -key empty.key -out empty.csr
$ openssl x509 -req -days 7305 -sha256 -CA ca.crt -CAkey ca.key -CAcreateserial -in empty.csr -out empty.crt
$ cat empty.crt ca.crt > fullchain.crt
$ openssl dhparam -dsaparam -out dhparams.pem 4096

写入 Nginx 配置文件

修改你的域名,将以下内容粘贴到 ./nginx/nginx.conf

请记得修改其中的 <Homeserver 域名>

user nginx;
worker_processes auto;
worker_cpu_affinity auto;

error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

worker_rlimit_nofile 51200;

events {
    use epoll;
    worker_connections 51200;
    multi_accept off;
    accept_mutex off;
}

http {
    include mime.types;
    default_type application/octet-stream;

    server_names_hash_bucket_size 128;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 50m;

    sendfile on;
    sendfile_max_chunk 512k;
    tcp_nopush on;

    keepalive_timeout 60;

    tcp_nodelay on;

    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 256k;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;
    gzip_vary on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable "MSIE [1-6]\.";
    server_tokens off;
    access_log off;

    # For Cloudflare CDN
    real_ip_header CF-Connecting-IP;
    set_real_ip_from 0.0.0.0/0;

    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name <**Homeserver 域名>**;

        ssl_certificate /etc/nginx/certs/fullchain.crt;
        ssl_certificate_key /etc/nginx/certs/empty.key;
        ssl_dhparam /etc/nginx/certs/dhparams.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:nginx:10m;
        ssl_session_tickets off;
        ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers off;

        add_header Strict-Transport-Security "max-age=63072000" always;

				 # Matrix Synapse
        location ~ ^(/_matrix|/.well-known/matrix) {
            proxy_pass http://synapse:8008;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-Proto https;
            proxy_set_header Host $host;
            proxy_read_timeout 600;
        }
    }
}

运行 Docker 镜像

基于 Synapse 提供的 docker-compose.yml 稍作修改,我制作了以下的文件,直接复制粘贴即可

services:
  postgres:
    hostname: postgres
    container_name: matrix-postgres
    image: ghcr.io/abcfy2/zhparser:17
    restart: always
    user: "1000:1000"
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U synapse"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - internal
  redis:
    hostname: redis
    container_name: matrix-redis
    image: redis:7
    restart: always
    volumes:
      - ./redis:/data
    networks:
      - internal
    healthcheck:
      test: ["CMD", "redis-cli","ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    command: ["redis-server", "--save", "60", "1", "--appendonly", "yes"]
  synapse:
    hostname: synapse
    container_name: matrix-synapse
    image: ghcr.io/element-hq/synapse:latest
    user: "1000:1000"
    volumes:
      - ./synapse:/data
      # External Modules
      - ./synapse_modules/search.py:/usr/local/lib/python3.12/site-packages/synapse/storage/databases/main/search.py
    healthcheck:
      test: ["CMD", "curl", "-f", "http://127.0.0.1:8008/_matrix/client/versions"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - internal
      - external
    restart: unless-stopped
  nginx:
    hostname: nginx
    container_name: matrix-nginx
    image: nginx:alpine
    restart: always
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - external
    ports:
      - 443:443

networks:
  internal:
    internal: true
  external:
    attachable: true

当所有的配置完成后,目录结构大致如下:

.
├── compose.yml
├── nginx
│   ├── certs
│   └── nginx.conf
├── redis
├── postgres
└── synapse
    ├── homeserver.yaml
    ├── <域名>.log.config
    └── <域名>.signing.key

没有任何问题的话,在项目目录下运行 docker compose up -d,运行上述镜像

首次运行

创建首个账户

当所有镜像成功运行后,我们需要执行 docker compose exec synapse /bin/bash 进入互命令行来创建用户

$ register_new_matrix_user http://localhost:8008 \
	-c /data/homeserver.yaml -a -u <用户名> -p <密码>

Sending registration request...
Success!

⭐ 只需要输入用户名部分即可,不需要输入完整的用户名+域名

使用 Cloudflare Worker 提供服务发现信息

🔑 如果需要让主域名成为你的 Matrix 服务域名(如 @me:mykeyvans.com),则这个步骤是必需的。如果只想使用子域名(如 @me:homeserver.mykeyvans.com),则这步可以跳过

服务发现是 Matrix 网络发现服务器位置的一种方式。我们的实际服务运行在子域名上,而我们需要让其他服务器/客户端知道我们想使用主域名,则需要提供 /.well-known/matrix 信息。

由于我们的主域名托管在 Cloudflare 上,同时可能主域名运行着其他一些其他服务,因此使用 Cloudflare Worker 就能够优雅地在不修改其他项目文件的情况下,为主域名添加这个服务发现信息

创建 Cloudflare Worker

在 Cloudflare Dashboard → Workers & Pages 中创建一个新的 Worker

使用 Edit Code 的功能,粘贴以下代码并修改其中的 Homeserver 域名

// Homeserver
const HOMESERVER = '**<你的Homeserver 域名>**'

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);

  if (url.pathname === '/.well-known/matrix/server') {
    return new Response(JSON.stringify({ "m.server": `${HOMESERVER}:443` }), {
      headers: { 'Content-Type': 'application/json' }
    });
  } else if (url.pathname.startsWith('/.well-known/matrix')) {
    const targetUrl = `https://${HOMESERVER}${url.pathname}`;
    return fetch(targetUrl, {
      method: request.method,
      headers: request.headers,
      body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null
    });
  } else {
    return new Response('Not Found', { status: 404 });
  }
}

这段代码的功能是:

  • 匹配 /.well-known/matrix 路径
  • 提供实际的服务器地址
  • 将其他服务发现的网络请求转发到 Matrix 服务器

粘贴并修改完毕之后,点击 Deploy 将代码部署到 Cloudflare 网络上

设置路由规则

在 Workers & Pages → Settings → Triggers 中,添加一个新的路由(Route)

  • Route:输入 <主域名>/.well-known/matrix/*
  • Zone :选择你的主域名

点击 Add Route 即可添加规则,之后你可以通过在浏览器中访问 https://<主域名>/.well-known/matrix/serverhttps://<主域名>/.well-known/matrix/server 来检查是否已经生效

🏃‍♂️ 开始使用

在上述所有的步骤都完成之后,就可以正式开始使用 Matrix 了!

在浏览器上打开 https://app.element.io ,切换 Homeserver 到你的根域名

登录界面

如果配置正确,你就可以输入用户名和密码,登录进你刚配置好的服务器了!

主界面

Now we can talk on Matrix!

Matrix - Decentralised and secure communication

🎉 拓展内容

在部署完服务器之后,接下来我还打算设置以下功能

  • 使用 Matrix Media Repo 储存文件
  • 支持中文搜索
  • 导入丰富的 Telegram 表情包
  • (还有更多,想到补充……)

敬请期待!

📎 参考文章

快速部署Matrix服务器,并使用Cloudflare Workers提供联邦验证

给 Matrix Synapse 添加中文搜索