Ksable' 小屋

ホーム留言板
友達ブログについて

# 便笺: Readest 私有化部署笔记

发表于 2025-07-28 13:48
更新于 2025-07-28 18:58
1612 字
9 min read

kissablecho

kissablecho 的个人博客 / 记录生活，分享技术 / 喜欢二次元和白丝。

128 記事

4 カテゴリー

7 タグ
ホーム
留言板
カテゴリー
タグ
アーカイブ
友達
ブログについて
一些实用的网站
astro-koharu 使用指南
音楽
个人 astro-koharu 主题修改记录便笺: [微信]不用发消息，如何知道自己有没有被拉黑?便笺: git 与 patch 便笺: Readest 私有化部署笔记便笺: VBS 消息轰炸便笺: Javascript 笔记 a ? b : c 便笺: 期末考试，一些人的成绩便笺: 2024-07-22 归档页面修改便笺: Markdown 备忘单便笺: hexo 插入图片 hello world
この投稿は「日本語」では表示できません。元の投稿を表示しています。
Readest 是一款现代开源电子书阅读器，用于沉浸式阅读。在 Github 上有 10.1k stars，可以说是一个非常棒的项目。然而，在GitHub项目页面上，仅提供了构建项目的指南，而未详细说明如何配置项目。然后我翻遍了整个互联网，发现啊，TM 全网没有一篇部署教程，全是介绍 Readest 的介绍推广。内容看起来几乎完全一样，十几篇文章相互抄似的。本文作为我个人私有化部署的笔记，目前仅完成了网页端的部署工作，仅供参考之用。

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 调试怎么卡得要命，网页一离开前台就给关掉了。手机性能超差。

気に入ったら、コメントを残してね～

日本語
中文 English 日本語

kissablecho

kissablecho 的个人博客 / 记录生活，分享技术 / 喜欢二次元和白丝。

128 記事

4 カテゴリー

7 タグ
ホーム
留言板
カテゴリー
タグ
アーカイブ
友達
ブログについて
一些实用的网站
astro-koharu 使用指南
音楽
个人 astro-koharu 主题修改记录便笺: [微信]不用发消息，如何知道自己有没有被拉黑?便笺: git 与 patch 便笺: Readest 私有化部署笔记便笺: VBS 消息轰炸便笺: Javascript 笔记 a ? b : c 便笺: 期末考试，一些人的成绩便笺: 2024-07-22 归档页面修改便笺: Markdown 备忘单便笺: hexo 插入图片 hello world