Featured image of post Readest官方自建同步服务器部署

Readest官方自建同步服务器部署

通过Readest Docker Compose及云服务器实现电子书同步

Readest 官方自建同步服务器部署指南

适用环境:Ubuntu + Nginx + Docker Compose
目标设备:Windows、Android
主要格式:EPUB、PDF、TXT
核对日期:2026-11-09(Asia/Shanghai)
正式部署范围:仅 Readest 官方仓库、官方容器镜像和官方自建网页端

1. 结论与方案边界

推荐架构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Windows 浏览器 / 可安装网页应用
Android 浏览器 / 可安装网页应用
                │
          HTTPS(同一域名)
                │
              Nginx
       ┌────────┼────────┐
       │        │        │
  Readest Web  Kong    MinIO
    :3000     :8000    :9000
                │
         Auth + PostgREST
                │
           PostgreSQL

官方 Docker Compose 栈包含 Readest Web、Supabase PostgreSQL、Kong、GoTrue、PostgREST 和 MinIO。书架、阅读进度、书签、高亮、笔记等结构化数据进入 PostgreSQL;电子书文件进入 MinIO。

本方案的正式客户端是官方自建网页端:

  • Windows:Edge、Chrome 等 Chromium 浏览器访问;浏览器提供“安装应用”时可安装为网页应用。
  • Android:Chrome 等浏览器访问;浏览器提供“安装应用”或“添加到主屏幕”时可安装。
  • 官方文档确认 Web 版首次访问后可离线工作,但是否显示完整 PWA 安装入口取决于浏览器、系统及站点条件。没有安装入口时,直接使用网页或添加桌面快捷方式即可。

本方案不把官方 Windows EXE、Android APK 连接自建后端列为正式步骤。截至核对日期,官方自建说明明确提供的是 Docker Web 客户端和后端栈,没有给出商店版/官网版原生客户端切换自建服务器的正式配置流程。社区客户端仅在文末作为未来可选项,本次不安装、不配置。

2. 能实现什么

需求本方案
EPUB、PDF、TXT 阅读支持
书籍文件同步支持,文件存入 MinIO
阅读进度同步支持
书签、高亮、笔记同步支持
Windows 与 Android 共用书架支持,通过同一自建网页端账户
全部书籍自动下载到每台设备不会;接收端按需下载
官方原生 EXE/APK 直接切换自建服务器不作为本方案已验证能力

Readest 把一本书拆成两类同步内容:

  1. 书架条目、封面、元数据、阅读状态、位置、书签、高亮和笔记会同步到登录设备。
  2. EPUB/PDF/TXT 等实际文件保留在云端,设备打开或主动下载时才获取,不会在后台下载整个书库。

因此,“自动同步书籍”表示书架与云端文件自动保持可用,并不表示每台设备都自动保存完整副本。

Readest 官方支持 EPUB、PDF 和 TXT。EPUB 的排版、目录和批注体验最完整;PDF 的选择、高亮能力取决于 PDF 是否含可用文本层;某些发送或导入路径可能把 TXT 转换为 EPUB,但仍可进入书库并同步。

3. 已核对的官方配置

发布本文前,已逐项对照 Readest 官方仓库 main 分支:

项目当前官方值或结论
Compose 文件docker/compose.yaml
环境变量模板docker/.env.example
Nginx 示例docker/nginx.conf.example
官方客户端镜像ghcr.io/readest/readest:latest
Readest Web主机端口 3000
Kong / Supabase API主机端口 8000
MinIO S3 API主机端口 9000
MinIO Console主机端口 9001
Supabase 路由/auth/v1/、/rest/v1/
默认 MinIO 存储桶readest-files
MinIO 反代要求保留原始 Host;上传位置取消请求体大小限制
自建开关SELF_HOSTED=true
更新后迁移运行 zz-readest-migrations.sh

本文没有沿用旧草案中未经核实的 client_max_body_size 1g;现行官方 Nginx 示例使用 client_max_body_size 0。本文也不使用针对旧版 compose.yaml 的批量 sed 替换,而是用 Docker Compose 官方支持的覆盖文件限制监听地址。

官方资料:

main 分支会继续变化。部署时记录 Git 提交号,更新前重新检查官方说明和差异。

4. 部署前准备

4.1 建议资源

这不是 Readest 官方硬性最低配置,而是完整数据库与对象存储栈的实用建议:

项目建议
Ubuntu22.04 LTS、24.04 LTS 或当前受 Docker 支持的 64 位 Ubuntu
CPU2 核或以上
内存4 GiB 建议值
磁盘系统余量至少 10 GiB,另加书库和备份空间
域名独立子域名,如 read.example.com
公网端口TCP 80、443

下文统一使用:

1
2
域名:read.example.com
安装目录:/opt/readest

执行时必须把 read.example.com 替换为真实域名。

4.2 DNS 和防火墙

在 DNS 服务商处添加:

类型主机记录值
Aread服务器公网 IPv4
AAAA可选服务器可用的公网 IPv6

如存在错误的 AAAA 记录,应删除或修正,否则部分设备会优先走不可用的 IPv6。

云平台安全组仅需允许 TCP 80、443。不要向公网开放 3000、8000、9000、9001。

Docker 官方提醒:容器发布端口可能绕过 UFW 规则。因此本文把四个容器端口直接绑定到 127.0.0.1,不能只依赖 UFW 阻止外网访问。

4.3 安装基础软件

1
2
3
4
5
sudo apt update
sudo apt install -y ca-certificates curl git nginx certbot python3

sudo systemctl enable --now nginx
nginx -v

如果 Docker Engine 和 Compose 插件已经安装,检查版本:

1
2
sudo docker version
sudo docker compose version

本文的无侵入端口覆盖文件需要 Docker Compose 2.24.4 或更高版本。

4.4 尚未安装 Docker 时

以下命令来自 Docker 当前的 Ubuntu 官方安装方法。已有 Docker 的服务器不要盲目卸载或覆盖,先核对现有安装来源和容器。

 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
sudo apt update
sudo apt install -y ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install -y \
  docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

sudo systemctl enable --now docker
sudo docker version
sudo docker compose version

确认 Compose 版本不低于 2.24.4,再继续。

5. 下载官方 Readest 项目

1
2
3
4
5
6
7
8
9
sudo mkdir -p /opt/readest
sudo chown "$USER":"$USER" /opt/readest

git clone --depth 1 \
  https://github.com/readest/readest.git \
  /opt/readest

cd /opt/readest
git rev-parse HEAD | tee DEPLOYED_COMMIT.txt

DEPLOYED_COMMIT.txt 用于记录本次实际部署的官方源码状态。以后排错或恢复时,不要只写“latest”。

先检查 Docker 目录:

1
2
3
4
cd /opt/readest
test -f docker/compose.yaml
test -f docker/.env.example
test -f docker/nginx.conf.example

6. 生成并填写环境变量

6.1 复制官方模板

1
2
3
cd /opt/readest
cp docker/.env.example docker/.env
chmod 600 docker/.env

6.2 离线生成随机密钥并写入 .env

Readest 官方要求:

  • POSTGRES_PASSWORD 至少 32 字符且足够随机;
  • JWT_SECRET 至少 32 字符;
  • ANON_KEY 与 SERVICE_ROLE_KEY 是由同一个 JWT_SECRET 使用 HS256 签名的 JWT;
  • ANON_KEY 的 payload 为 {"role":"anon"};
  • SERVICE_ROLE_KEY 的 payload 为 {"role":"service_role"};
  • MINIO_ROOT_PASSWORD 使用强随机密码。

下面的本地 Python 辅助脚本不联网、不安装第三方包,只按上述官方规则生成并更新已复制的模板。运行前先设置真实域名:

 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
cd /opt/readest

read -rp "Readest 域名(例如 read.example.com): " READEST_DOMAIN
export READEST_DOMAIN

python3 - <<'PY'
from pathlib import Path
import base64
import hashlib
import hmac
import json
import os
import re
import secrets

domain = os.environ["READEST_DOMAIN"].strip()
if not domain or "://" in domain or "/" in domain:
    raise SystemExit("域名只能填写主机名,不要包含协议或路径")

env_path = Path("docker/.env")
text = env_path.read_text(encoding="utf-8")

def b64url(data):
    if not isinstance(data, bytes):
        data = json.dumps(
            data, separators=(",", ":"), ensure_ascii=True
        ).encode("utf-8")
    return base64.urlsafe_b64encode(data).decode().rstrip("=")

jwt_secret = secrets.token_urlsafe(48)

def make_jwt(role):
    header = b64url({"alg": "HS256", "typ": "JWT"})
    payload = b64url({"role": role})
    message = f"{header}.{payload}"
    signature = hmac.new(
        jwt_secret.encode(),
        message.encode(),
        hashlib.sha256,
    ).digest()
    return f"{message}.{b64url(signature)}"

url = f"https://{domain}"
values = {
    "HOST_IP": domain,
    "SITE_URL": url,
    "API_EXTERNAL_URL": url,
    "ADDITIONAL_REDIRECT_URLS": url + "/**",
    "SUPABASE_PUBLIC_URL": url,
    "S3_PUBLIC_ENDPOINT": url,
    "POSTGRES_PASSWORD": secrets.token_urlsafe(48),
    "JWT_SECRET": jwt_secret,
    "ANON_KEY": make_jwt("anon"),
    "SERVICE_ROLE_KEY": make_jwt("service_role"),
    "MINIO_ROOT_USER": "readestadmin",
    "MINIO_ROOT_PASSWORD": secrets.token_urlsafe(48),
    "S3_BUCKET_NAME": "readest-files",
    "SELF_HOSTED": "true",
    "STORAGE_FIXED_QUOTA": str(10 * 1024**3),
    "DISABLE_SIGNUP": "false",
    "ENABLE_EMAIL_SIGNUP": "true",
    "ENABLE_EMAIL_AUTOCONFIRM": "true",
    "ENABLE_ANONYMOUS_USERS": "false",
}

for key, value in values.items():
    pattern = re.compile(
        rf"(?m)^[ \t]*#?[ \t]*{re.escape(key)}=.*$"
    )
    line = f"{key}={value}"
    if pattern.search(text):
        text = pattern.sub(line, text, count=1)
    else:
        text += f"\n{line}\n"

env_path.write_text(text, encoding="utf-8")
os.chmod(env_path, 0o600)
print("docker/.env 已更新;密钥未发送到网络。")
PY

unset READEST_DOMAIN

STORAGE_FIXED_QUOTA=10737418240 表示每用户逻辑配额 10 GiB,不会自动扩大服务器磁盘。可按实际容量修改。

6.3 人工复核非秘密项

不要把 .env 全文粘贴到工单、聊天或公开仓库。只检查必要的非秘密值:

1
2
3
4
cd /opt/readest
grep -E \
  '^(HOST_IP|SITE_URL|API_EXTERNAL_URL|ADDITIONAL_REDIRECT_URLS|SUPABASE_PUBLIC_URL|S3_PUBLIC_ENDPOINT|S3_BUCKET_NAME|SELF_HOSTED|STORAGE_FIXED_QUOTA|DISABLE_SIGNUP)=' \
  docker/.env

预期域名相关值全部指向同一个 HTTPS 源:

1
2
3
4
5
6
7
8
HOST_IP=read.example.com
SITE_URL=https://read.example.com
API_EXTERNAL_URL=https://read.example.com
ADDITIONAL_REDIRECT_URLS=https://read.example.com/**
SUPABASE_PUBLIC_URL=https://read.example.com
S3_PUBLIC_ENDPOINT=https://read.example.com
S3_BUCKET_NAME=readest-files
SELF_HOSTED=true

不要公开:

  • POSTGRES_PASSWORD
  • JWT_SECRET
  • SERVICE_ROLE_KEY
  • MINIO_ROOT_PASSWORD
  • 完整 .env

ANON_KEY 会由网页客户端使用,但仍应从服务器配置中复制,不应随意替换;SERVICE_ROLE_KEY 具有高权限,绝不能放入客户端设置或公开页面。

7. 仅在本机发布容器端口

官方 compose.yaml 默认发布 3000、8000、9000、9001。官方 Nginx 示例建议在反代部署中绑定到回环地址。

为了保留官方 compose.yaml 原样并便于更新,创建 docker/compose.override.yaml:

1
2
cd /opt/readest/docker
nano compose.override.yaml

写入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
services:
  kong:
    ports: !override
      - "127.0.0.1:${KONG_HTTP_PORT:-8000}:8000/tcp"

  minio:
    ports: !override
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9001:9001"

  client:
    ports: !override
      - "127.0.0.1:3000:3000"

!override 需要 Docker Compose 2.24.4 或更高版本。不要删除这个标记,否则 Compose 可能把新旧端口列表合并,重新暴露公网端口。

验证合并配置:

1
2
3
cd /opt/readest/docker
sudo docker compose config --quiet
sudo docker compose config

在完整输出的 ports 项中,应看到 host_ip: 127.0.0.1。如果 docker compose config 报不认识 !override,先升级 Compose 插件,不要启动服务。

8. 启动官方 Compose 栈

1
2
3
4
5
cd /opt/readest/docker

sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps

查看关键日志:

1
2
sudo docker compose logs --tail=100 \
  client db auth rest kong minio minio-setup

minio-setup 是一次性创建存储桶的辅助容器,成功执行后退出是正常现象。其余长期服务应处于运行状态;MinIO 健康检查应通过。

检查监听地址:

1
sudo ss -lntp | grep -E ':(3000|8000|9000|9001)\b'

所有匹配项都应只监听 127.0.0.1,不应出现 0.0.0.0、[::] 或公网 IP。

在服务器本机测试 Web 客户端:

1
curl -I http://127.0.0.1:3000

9. 配置 Nginx 和 HTTPS

9.1 先配置 HTTP 证书验证站点

1
2
sudo mkdir -p /var/www/letsencrypt
sudo nano /etc/nginx/sites-available/readest.conf

先写入以下临时配置,并替换域名:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    listen 80;
    listen [::]:80;
    server_name read.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        default_type text/plain;
        return 200 "Readest certificate bootstrap\n";
    }
}

启用站点:

1
2
3
4
5
6
sudo ln -s \
  /etc/nginx/sites-available/readest.conf \
  /etc/nginx/sites-enabled/readest.conf

sudo nginx -t
sudo systemctl reload nginx

如果符号链接已存在,不要重复创建;直接继续检查配置即可。

从外网确认 http://read.example.com 能访问后申请证书:

1
2
3
4
sudo certbot certonly \
  --webroot \
  -w /var/www/letsencrypt \
  -d read.example.com

证书通常位于:

1
2
/etc/letsencrypt/live/read.example.com/fullchain.pem
/etc/letsencrypt/live/read.example.com/privkey.pem

9.2 写入最终 HTTPS 反向代理

1
sudo nano /etc/nginx/sites-available/readest.conf

用下面的完整配置替换临时配置。该路由结构与 Readest 官方 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
server {
    listen 80;
    listen [::]:80;
    server_name read.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    # Ubuntu 仓库中的 Nginx 通常使用这一兼容写法。
    # Nginx 1.25.1 及以上也可改为 listen 443 ssl; 加 http2 on;。
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name read.example.com;

    ssl_certificate
        /etc/letsencrypt/live/read.example.com/fullchain.pem;
    ssl_certificate_key
        /etc/letsencrypt/live/read.example.com/privkey.pem;

    # Readest 官方网页端
    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }

    # Supabase Auth 与 PostgREST,经 Kong 转发
    location ~ ^/(auth|rest)/v1/ {
        proxy_pass http://127.0.0.1:8000;
        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;
    }

    # MinIO 路径式预签名 URL。
    # readest-files 必须与 S3_BUCKET_NAME 完全一致。
    location /readest-files/ {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Host $host;
        client_max_body_size 0;
        proxy_request_buffering off;
    }
}

关键点:

  • /auth/v1/ 和 /rest/v1/ 必须转发到 Kong 的 8000。
  • /readest-files/ 必须转发到 MinIO 的 9000。
  • 路径首段 readest-files 必须与 S3_BUCKET_NAME 一致。
  • MinIO 的预签名 URL 会校验 Host,不能在代理中改成其他主机名。
  • client_max_body_size 0 是官方示例的当前写法,用于避免大文件上传被 Nginx 截断。
  • MinIO Console 的 9001 不需要公网 Nginx 路由。

应用并验证:

1
2
3
4
5
sudo nginx -t
sudo systemctl reload nginx

curl -I https://read.example.com
curl -i https://read.example.com/auth/v1/health

第二条命令用于确认 Auth 路由能够到达后端;重点是不能出现 Nginx 404、502 或 TLS 错误。

检查证书续期:

1
2
sudo certbot renew --dry-run
systemctl list-timers | grep -E 'certbot|snap.certbot'

10. 首次注册和关闭公开注册

10.1 创建第一个账户

在 Windows 浏览器打开:

1
https://read.example.com

使用邮箱和强密码注册。这个账户属于你的自建服务,与 Readest 官方云账户不是同一套账户数据。

本文默认:

1
ENABLE_EMAIL_AUTOCONFIRM=true

因此无需邮件确认即可建立首个账户。由于没有配置 SMTP,密码重置邮件通常也不可用,务必把账户密码和服务器备份妥善保存。

10.2 注册完成后立即关闭公开注册

1
2
cd /opt/readest/docker
nano .env

改为:

1
DISABLE_SIGNUP=true

应用:

1
2
sudo docker compose up -d --force-recreate auth
sudo docker compose ps auth

使用浏览器无痕窗口尝试注册新账户,确认注册已被拒绝;现有账户登录应保持正常。

如果将来需要多用户:

  1. 临时打开注册或配置 SMTP;
  2. 创建所需账户;
  3. 再次关闭注册;
  4. 不要长期保留“自动确认 + 公开注册”。

11. 启用 EPUB/PDF/TXT 与阅读数据同步

登录后进入:

1
Main Menu → Advanced Settings → Data Sync

确认启用:

  • Books:书籍文件与书架元数据;
  • Reading Progress:最后阅读位置、书签和每本书的偏好;
  • Annotations:高亮与笔记。

建议用三本小型测试文件验收:

  1. Windows 端导入 1 本 EPUB、1 个含文本层的 PDF、1 个 TXT。
  2. 等待同步完成,刷新书架。
  3. Android 端登录同一账户。
  4. 确认三本书的书架条目和封面出现。
  5. 分别打开书籍,让设备按需下载文件。
  6. 在 Windows 端改变阅读位置并创建书签、高亮、笔记。
  7. Android 端重新打开相同书籍,验证位置和批注。
  8. 在 Android 端新增笔记,再回到 Windows 验证双向同步。

不要只根据“封面出现”判断同步成功;封面、条目、实际书籍文件和批注是不同的数据层。

12. Windows 和 Android 使用方式

12.1 Windows

推荐 Edge 或 Chrome:

  1. 打开 https://read.example.com。
  2. 登录自建账户。
  3. 浏览器地址栏或菜单出现“安装应用”时选择安装。
  4. 没有安装入口时,创建站点快捷方式并固定到任务栏或开始菜单。
  5. 第一次离线使用前,先在线打开网页和需要离线阅读的书籍。

12.2 Android

  1. 用 Chrome 打开 https://read.example.com。
  2. 登录同一自建账户。
  3. 菜单中有“安装应用”时选择安装;否则选择“添加到主屏幕”。
  4. 在线打开需要离线阅读的书,使文件缓存到设备。
  5. 断网测试已下载书籍能否打开。

浏览器和系统可能回收站点存储。重要书籍不要只保留在手机浏览器缓存中,服务器备份才是长期副本。

13. 迁移现有 Readest 数据

在原有 Readest 中进入:

1
Main Menu → Advanced Settings → Backup & Restore

导出完整 ZIP。官方文档说明,书库备份可包含书籍文件、元数据、阅读状态、分组、高亮、笔记和应用设置。

迁移原则:

  1. 不要先卸载原客户端或清理原数据。
  2. 在自建网页端先导入少量备份,验证书籍、位置和笔记。
  3. 确认 Windows 与 Android 双端同步正常后,再迁移完整书库。
  4. 备份默认不加密;只有确有需要时才选择包含账户凭据,并把文件放入加密存储。

自建账户与官方云账户相互独立,不能假设登录自建站点后原官方云书库会自动出现。

14. 备份策略

至少保留两层备份:

  • Readest 应用内 ZIP:便于用户级迁移和快速恢复。
  • 服务器冷备份:保存数据库卷、MinIO 卷、配置卷、.env 和 Nginx 配置。

建议采用 3-2-1 原则:至少 3 份副本、2 种介质、1 份异地。

14.1 确认卷名

官方 compose.yaml 顶层项目名是 readest,通常创建:

1
2
3
readest_db-data
readest_db-config
readest_minio-data

备份前仍应实际检查:

1
sudo docker volume ls --format '{{.Name}}' | grep '^readest_'

14.2 一致性冷备份

下面会短暂停止 Readest。先确认备份磁盘空间充足:

1
2
sudo sh -c 'du -sh /var/lib/docker/volumes/readest_*'
df -h

开始备份:

 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
cd /opt/readest/docker

BACKUP_DIR="/var/backups/readest/$(date +%F_%H%M%S)"
sudo install -d -m 700 -o "$USER" -g "$USER" "$BACKUP_DIR"

sudo docker compose stop

for volume in \
  readest_db-data \
  readest_db-config \
  readest_minio-data
do
  sudo docker run --rm \
    -v "${volume}:/source:ro" \
    -v "${BACKUP_DIR}:/backup" \
    alpine:latest \
    sh -c "cd /source && tar -czf /backup/${volume}.tar.gz ."
done

sudo cp /opt/readest/docker/.env "$BACKUP_DIR/"
sudo cp /opt/readest/docker/compose.override.yaml "$BACKUP_DIR/"
sudo cp /opt/readest/DEPLOYED_COMMIT.txt "$BACKUP_DIR/"
sudo cp /etc/nginx/sites-available/readest.conf "$BACKUP_DIR/nginx-readest.conf"

sudo sha256sum "$BACKUP_DIR"/* | \
  sudo tee "$BACKUP_DIR/SHA256SUMS"

sudo docker compose start
sudo docker compose ps

echo "备份目录:$BACKUP_DIR"

注意:

  • 如果任何备份命令失败,也要执行 sudo docker compose start 恢复服务。
  • 首次运行会拉取官方 alpine 工具镜像。
  • .env 和数据库备份含高敏感密钥与账户数据,异地保存前必须加密。
  • 定期做恢复演练;只有成功恢复过的备份才算可靠。
  • 永远不要把 docker compose down -v 当作普通停止命令,它会删除数据卷。

14.3 恢复思路

恢复应在全新或已确认可覆盖的目标环境中进行:

  1. 安装 Docker、Nginx,恢复与备份一致的 Readest Git 提交。
  2. 恢复 docker/.env、compose.override.yaml 和 Nginx 配置。
  3. 在未启动 Readest 前创建三个同名卷。
  4. 用临时 Alpine 容器把各 tar.gz 解压到对应空卷。
  5. 启动 Compose,检查数据库、登录、书籍与批注。
  6. 如恢复到新域名,必须同步修改 SITE_URL、API_EXTERNAL_URL、ADDITIONAL_REDIRECT_URLS、SUPABASE_PUBLIC_URL、S3_PUBLIC_ENDPOINT 和 Nginx 证书/域名。

不要把恢复操作直接用于仍有有效数据的生产卷;先在测试机演练。

15. 更新流程

更新前先做完整备份,并阅读 Readest Release Notes 与 docker/README.md。

查看本地状态和官方 Docker 文件差异:

1
2
3
4
5
6
7
8
9
cd /opt/readest
git status --short
git fetch --prune origin
git diff HEAD..origin/main -- \
  docker/README.md \
  docker/compose.yaml \
  docker/.env.example \
  docker/nginx.conf.example \
  docker/volumes

确认差异后更新源码:

1
2
3
cd /opt/readest
git pull --ff-only
git rev-parse HEAD | tee DEPLOYED_COMMIT.txt

更新容器并执行官方数据库迁移:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cd /opt/readest/docker

sudo docker compose config --quiet
sudo docker compose pull
sudo docker compose up -d
sudo docker compose exec db \
  /docker-entrypoint-initdb.d/zz-readest-migrations.sh

sudo docker compose ps
sudo docker compose logs --tail=100 client db auth rest kong minio

官方说明指出:拉取新版客户端镜像不会自动修改已有数据库卷,而首次启动钩子只在空数据库卷运行,因此已有部署升级后必须手动运行迁移脚本。脚本会记录已应用迁移,正常情况下可重复执行。

生产环境可把 READEST_IMAGE 从滚动的 latest 改为官方发布的具体版本标签,先在测试环境验证后再升级。不要使用来源不明的镜像。

16. 日常检查与排错

16.1 状态和日志

1
2
3
4
5
6
cd /opt/readest/docker

sudo docker compose ps
sudo docker compose logs -f --tail=100 client
sudo docker compose logs -f --tail=100 auth kong rest
sudo docker compose logs -f --tail=100 minio

16.2 常见症状

网页出现 502

1
2
3
curl -I http://127.0.0.1:3000
sudo docker compose ps
sudo docker compose logs --tail=100 client

登录或注册失败

1
2
curl -i https://read.example.com/auth/v1/health
sudo docker compose logs --tail=100 auth kong

书籍上传失败或签名错误

重点核对:

  • S3_PUBLIC_ENDPOINT=https://read.example.com;
  • S3_BUCKET_NAME=readest-files;
  • Nginx 路径为 /readest-files/;
  • MinIO 位置保留 proxy_set_header Host $host;;
  • client_max_body_size 0;;
  • 没有 CDN、WAF 或上游代理改写 Host、路径或限制请求体。

端口意外暴露公网

1
2
sudo ss -lntp | grep -E ':(3000|8000|9000|9001)\b'
sudo docker compose config

如果发现 0.0.0.0 或 [::],先停止栈,修复 compose.override.yaml,确认 Compose 版本支持 !override 后再启动。

中文或日文字体加载失败

官方 README 说明:部分 CJK Web 字体默认来自 Readest CDN,而 CDN 的 CORS 只允许 readest.com 来源。自定义域名如需这些字体,应把相应字体包镜像到自己的域名,并设置:

1
FONT_BASE_URL=https://read.example.com/fonts

这是可选项;系统字体和 Google Fonts 不受此设置影响。不要从不可信来源下载字体文件。

17. 安全清单

  • 只对公网开放 80、443。
  • 3000、8000、9000、9001 只监听 127.0.0.1。
  • 使用有效 HTTPS 证书并测试自动续期。
  • POSTGRES_PASSWORD、JWT_SECRET、SERVICE_ROLE_KEY、MINIO_ROOT_PASSWORD 均为随机强密钥。
  • docker/.env 权限为 600。
  • 首个账户创建后已设置 DISABLE_SIGNUP=true。
  • 没有把 MinIO Console 9001 代理到公网。
  • 没有把 SERVICE_ROLE_KEY 填到任何客户端或社区程序。
  • 不执行 docker compose down -v。
  • 更新前做冷备份,更新后执行官方迁移脚本。
  • 备份已加密并有异地副本。
  • 至少做过一次测试恢复。
  • 定期检查磁盘、证书、容器健康状态和官方安全更新。

需要临时访问 MinIO Console 时,优先使用 SSH 隧道,而不是公开 9001:

1
ssh -L 9001:127.0.0.1:9001 your-user@your-server

然后在本机打开 http://127.0.0.1:9001。结束后关闭 SSH 会话。

18. 验收清单

  • https://read.example.com 证书有效。
  • HTTP 自动跳转 HTTPS。
  • /auth/v1/health 能到达 Auth 后端。
  • 新账户可登录。
  • 关闭注册后不能再创建新账户。
  • EPUB 可上传、打开、跨端按需下载。
  • PDF 可上传、打开、同步阅读位置。
  • TXT 可导入、打开、跨端获取。
  • 阅读进度双向同步。
  • 书签、高亮、笔记双向同步。
  • 手机离线可打开已经下载的书。
  • 3000、8000、9000、9001 没有公网监听。
  • 应用内 ZIP 已导出。
  • 服务器冷备份已生成并校验。

19. 未来可选:社区客户端

如果以后必须使用可配置自建服务器的 Windows/Android 原生客户端,可以单独评估社区项目:

该项目不是 Readest 官方发行版。本次正式部署不安装、不下载、不填写任何服务器密钥到社区客户端。

未来评估时至少检查:

  1. 最近提交和发行时间;
  2. 是否跟上官方数据库结构与同步协议;
  3. 构建产物来源和签名;
  4. 是否只需要公开的 ANON_KEY,绝不能要求 SERVICE_ROLE_KEY、数据库密码或 JWT Secret;
  5. 先用测试账户和少量测试书验证;
  6. 保留官方自建网页端作为可回退入口。

在完成上述审计前,不建议用社区客户端替换现有正式阅读环境。


附录:正式部署范围摘要

本指南正式启用:

  • Readest 官方 GitHub 仓库;
  • Readest 官方 GHCR 镜像;
  • 官方 Docker Compose 后端栈;
  • Nginx 单域名 HTTPS;
  • 官方自建网页端;
  • 浏览器提供时的 PWA/站点安装;
  • EPUB、PDF、TXT 文件与阅读数据同步;
  • 应用内备份和服务器冷备份。

本指南暂不启用:

  • 社区改版 Windows 客户端;
  • 社区改版 Android 客户端;
  • BookOrbit、Grimmory 等社区服务;
  • 外部 WebDAV/S3 作为第二同步层;
  • 对公网开放 MinIO Console;
  • 未经官方仓库验证的 Compose、环境变量或 Nginx 路由。
使用 Hugo 构建
主题 Stack 由 Jimmy 设计