抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Readest 是一款现代开源电子书阅读器,用于沉浸式阅读。

在 Github 上有 10.1k stars,可以说是一个非常棒的项目。

然而,在GitHub项目页面上,仅提供了构建项目的指南,而未详细说明如何配置项目。然后我翻遍了整个互联网,发现啊,TM 全网没有一篇部署教程,全是介绍 Readest 的介绍推广。内容看起来几乎完全一样,十几篇文章相互抄似的。

本文作为我个人私有化部署的笔记,目前仅完成了网页端的部署工作,仅供参考之用。

我的部署环境

Netlify Build

Runtime: Next.js
Base directory: /
Package directory: Not set
Build command: git submodule update --init --recursive && pnpm i && pnpm --filter @readest/readest-app setup-pdfjs && export NODE_OPTIONS=--max-old-space-size=4096 && pnpm --filter @readest/readest-app build-web
Publish directory: apps/readest-app/.next

Node.js: 22.x
Build image: Ubuntu Noble 24.04 (default)

ENV

项目中包含一个名为 .env.local.example 的文件,位于 apps/readest-app/.env.local.example。然而,该示例文件的内容并不完整。整个项目共依赖45个环境变量,而该示例文件仅提供了20个。请参照以下配置进行补充:

以下是必须配置的环境变量列表:

  • NEXT_PUBLIC_API_BASE_URL
  • NEXT_PUBLIC_POSTHOG_KEY
  • NEXT_PUBLIC_POSTHOG_HOST
  • NEXT_PUBLIC_SUPABASE_URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEY
  • SUPABASE_ADMIN_KEY
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
# ====== 核心应用配置 ======
NEXT_PUBLIC_APP_PLATFORM=web # 建议不要填它 应用平台: web/tauri
NEXT_PUBLIC_API_BASE_URL=https://your-api-base-url.com # API基础URL
NEXT_PUBLIC_NODE_BASE_URL= # Node.js服务基础URL(可选)

# ====== 分析监控 ======
NEXT_PUBLIC_POSTHOG_KEY=YOUR_POSTHOG_KEY # PostHog分析密钥
NEXT_PUBLIC_POSTHOG_HOST=YOUR_POSTHOG_HOST # PostHog服务地址
NEXT_PUBLIC_DEFAULT_POSTHOG_URL_BASE64= # PostHog备用URL(base64编码)
NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64= # PostHog备用密钥(base64编码)

# ====== 数据库 & 认证 ======
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL # Supabase项目URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY # Supabase匿名密钥
SUPABASE_ADMIN_KEY=YOUR_SUPABASE_ADMIN_KEY # Supabase管理员密钥
NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64= # 备用Supabase URL(base64)
NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64= # 备用Supabase密钥(base64)

# ====== 支付 & 订阅 ======
# Stripe配置
STRIPE_SECRET_KEY= # Stripe生产环境密钥
STRIPE_SECRET_KEY_DEV= # Stripe开发环境密钥
STRIPE_WEBHOOK_SECRET= # Stripe webhook验证密钥
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_BASE64= # Stripe生产公钥(base64)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_DEV_BASE64= # Stripe开发公钥(base64)

# Apple应用内支付
APPLE_IAP_KEY_ID= # Apple IAP密钥ID
APPLE_IAP_ISSUER_ID= # Apple IAP发行者ID
APPLE_IAP_BUNDLE_ID= # Apple应用包ID
APPLE_IAP_PRIVATE_KEY_BASE64= # Apple私钥(base64编码)

# ====== 文件存储 ======
# 存储类型: r2/s3
NEXT_PUBLIC_OBJECT_STORAGE_TYPE=r2

# Cloudflare R2配置
R2_ACCESS_KEY_ID=YOUR_R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY=YOUR_R2_SECRET_ACCESS_KEY
R2_BUCKET_NAME=YOUR_R2_BUCKET_NAME
R2_ACCOUNT_ID=YOUR_R2_ACCOUNT_ID
R2_REGION=auto # R2存储区域

# AWS S3配置
S3_ENDPOINT= # S3服务端点
S3_ACCESS_KEY_ID= # S3访问密钥
S3_SECRET_ACCESS_KEY= # S3密钥
S3_BUCKET_NAME= # S3存储桶名称
S3_REGION= # S3存储区域

# ====== 资源配额 ======
NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824 # 固定存储配额(字节)
NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA= # 固定翻译配额(可选)

# ====== 翻译服务 ======
DEEPL_PRO_API_KEYS=YOUR_DEEPL_PRO_API_KEYS # DeepL专业版API密钥
DEEPL_FREE_API_KEYS=YOUR_DEEPL_FREE_API_KEYS # DeepL免费版API密钥
DEEPL_X_FINGERPRINT= # DeepL安全指纹
DEEPL_PRO_API= # DeepL专业版API端点
DEEPL_FREE_API= # DeepL免费版API端点

# ====== 高级功能 ======
NEXT_PUBLIC_USE_APPLE_SIGN_IN=false # 启用Apple登录
NEXT_PUBLIC_DISABLE_UPDATER= # 禁用自动更新
NEXT_PUBLIC_DIST_CHANNEL=readest # 发布渠道
ENABLE_IAP_API_TESTS=false # 启用IAP测试
USE_CUSTOM_OAUTH= # 使用自定义OAuth

# ====== 服务器配置 ======
PROTOCOL=http # 服务协议: http/https
HOST=localhost:3000 # 服务主机
ANALYZE=false # 启用bundle分析
R2_TOKEN_VALUE=YOUR_R2_TOKEN_VALUE # R2访问令牌(可选)

数据库 Supabase 配置

创建数据库表

执行以下 SQL 语句以创建表。

官方的 Supabase Tables Schema for Sync APIpublic.books 表缺少了两列 source_title metadatatransform.ts (apps/readest-app/src/utils/transform.ts) 会尝试向这两列写入数据,public.books 表缺少这两列直接报错。

总之运行我这段 SQL 就行了。

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
create table public.books (
user_id uuid not null,
book_hash text not null,
format text null, -- 'EPUB' | 'PDF' | 'MOBI' | 'CBZ' | 'FB2' | 'FBZ'
title text null,
source_title text null,
author text null,
"group" text null,
tags text[] null,
metadata text null,
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
deleted_at timestamp with time zone null,
uploaded_at timestamp with time zone null,
progress integer[] null,
group_id text null,
group_name text null,
constraint books_pkey primary key (user_id, book_hash),
constraint books_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.books ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_books ON public.books
FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_books ON public.books
FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_books ON public.books
FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_books ON public.books
FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

create table public.book_configs (
user_id uuid not null,
book_hash text not null,
location text null,
progress jsonb null,
search_config jsonb null,
view_settings jsonb null,
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
deleted_at timestamp with time zone null,
constraint book_configs_pkey primary key (user_id, book_hash),
constraint book_configs_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.book_configs ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_book_configs ON public.book_configs
FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_book_configs ON public.book_configs
FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_book_configs ON public.book_configs
FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_book_configs ON public.book_configs
FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

create table public.book_notes (
user_id uuid not null,
book_hash text not null,
id text not null,
type text null,
cfi text null,
text text null,
style text null,
color text null,
note text null,
created_at timestamp with time zone null default now(),
updated_at timestamp with time zone null default now(),
deleted_at timestamp with time zone null,
constraint book_notes_pkey primary key (user_id, book_hash, id),
constraint book_notes_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.book_notes ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_book_notes ON public.book_notes
FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_book_notes ON public.book_notes
FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_book_notes ON public.book_notes
FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_book_notes ON public.book_notes
FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

-- Create the `files` table
create table public.files (
id uuid not null default gen_random_uuid (),
user_id uuid not null,
book_hash text null,
file_key text not null,
file_size bigint not null,
created_at timestamp with time zone null default now(),
deleted_at timestamp with time zone null,
constraint files_pkey primary key (id),
constraint files_file_key_key unique (file_key),
constraint files_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

-- Add an index for efficient querying by user_id and deleted_at
create index idx_files_user_id_deleted_at
on public.files (user_id, deleted_at);

create index idx_files_file_key
on public.files (file_key);

create index idx_files_file_key_deleted_at
on public.files (file_key, deleted_at);

-- Enable RLS on the `files` table
alter table public.files enable row level security;

create policy "Users can insert their own files"
on public.files
for insert
with check (
auth.uid() = user_id
);


create policy "Users can view their own active files"
on public.files
for select
using (
auth.uid() = user_id and deleted_at is null
);


create policy "Users can soft-delete their own files"
on public.files
for update
using (
auth.uid() = user_id
)
with check (
deleted_at is null or deleted_at > now()
);

create policy "Users can delete their own files permanently"
on public.files
for delete
using (
auth.uid() = user_id
);

配置 Supabase 中的 site URL

在 Supabase 仪表盘中找到 Authentication(左侧) > site URL 把 site URL 改为网站 URL

配置用户

Readest 的用户是在 Supabase 管理的,可以在 Authentication > Users 新增用户,删除用户。

在 Authentication > Policies 可以设置登录/注册方式 (Email/Google/Github/Apple),但 Readest 还是会在登录界面显示这些选项,只是禁用的方式登录失败。

还可以在 Authentication > Policies 禁用用户注册。

我这是全程在手机上操作的,手机巨垃圾。
天玑 9300+ 16.0GB 运行内存,我怎么都想不明白在 Termux 调试怎么卡得要命,网页一离开前台就给关掉了。手机性能超差。

评论