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
# ====== 核心应用配置 ======
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 API 中 public.books 表缺少了两列 source_title metadata, transform.ts (apps/readest-app/src/utils/transform.ts) 会尝试向这两列写入数据,public.books 表缺少这两列直接报错。
总之运行我这段 SQL 就行了。
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 调试怎么卡得要命,网页一离开前台就给关掉了。手机性能超差。
喜欢的话,留下你的评论吧~