Ubuntu 24.04 安装 Jelu 0.88.2 指南

在 Ubuntu 24.04 上从零安装 Jelu 0.88.2 自托管阅读追踪器:官方 jar 下载与 sha256 校验、systemd 托管、JVM 内存限定、离线安装备选方案、管理员初始化与四类启动故障排障实录,全部命令实测。

JeluJeluUbuntu24.04Java平台NAS 自托管2026/10/28

Ubuntu 24.04 安装 Jelu 0.88.2 指南

目标:在 Ubuntu 24.04 服务器上从零安装并以 systemd 托管 Jelu 0.88.2(自托管阅读追踪器,Spring Boot fat jar 形态),覆盖「官方 jar 在线安装」与「断网内网离线安装」两套方案、管理员账户初始化、JVM 内存限定与启动故障排障实录;全部命令已在 Ubuntu 24.04.5 LTS + OpenJDK 21.0.12.1 + Jelu v0.88.2 环境逐条实测。组件定位与选型背景见《《Jelu 介绍与选型指南》》。

一、环境信息

本文所有命令与回显均来自以下实测环境:

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64,/etc/os-release 中 PRETTY_NAME="Ubuntu 24.04.5 LTS");
  • Java 运行时:OpenJDK 21.0.12.1(openjdk-21-jre-headless,apt 官方源版本 21.0.12.1+1-1~24.04.4);
  • Jelu 版本:v0.88.2(2026-10-07 发布,查询日期 2026-10-28;jar 资产 jelu-0.88.2.jar,108,216,402 字节);
  • 服务管理:systemd,单元名 jelu.service(enabled,开机自启);
  • 监听端口:11111(Jelu 默认端口),可选 nginx 1.24.0 反向代理 80 端口;
  • 安装路径:jar 存放 /opt/jelu/,数据与工作目录 /var/lib/jelu/;
  • 运行账户:专用系统用户 jelu(实测 uid/gid 997,shell 为 /usr/sbin/nologin);
  • JVM 内存:-Xms256m -Xmx512m(限定在 256–512MB 区间,见 4.7 节与 7.2 节);
  • 可选依赖:calibre(提供 fetch-ebook-metadata 元数据抓取工具)、nginx(反向代理)。

二、前置条件

2.1 Java 运行时(必需)

Jelu 是 Spring Boot 应用,官方安装文档(bayang.github.io/jelu-web/installation/jar/)要求「Jelu needs at least Java 11」。本文选用 Ubuntu 24.04 官方源自带的 OpenJDK 21(实测通过):

sudo apt-get update
sudo apt-get install -y openjdk-21-jre-headless

已安装环境下重复执行上述安装命令的实测回显(输出节选):

Reading state information...
openjdk-21-jre-headless is already the newest version (21.0.12.1+1-1~24.04.4).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.

验证安装结果:

java -version

实测回显:

openjdk version "21.0.12.1" 2026-08-18
OpenJDK Runtime Environment (build 21.0.12.1+1-1-24.04.4-Ubuntu)
OpenJDK 64-Bit Server VM (build 21.0.12.1+1-1-24.04.4-Ubuntu, mixed mode, sharing)

💡 技术拓展:Ubuntu 源里的 OpenJDK 包有三种常见形态——openjdk-21-jre-headless(无图形依赖、体积最小的运行时,服务器首选)、openjdk-21-jre(含 AWT/Swing 图形栈,桌面场景用)、openjdk-21-jdk(含 javac 等编译工具,开发场景用)。Jelu 只需运行 jar,用 jre-headless 即可;本文实测采用 openjdk-21-jre-headless。

2.2 硬件资源

  • 内存:JVM 堆限定 256–512MB;实测实例(512MB 堆)常驻内存 477.2M(systemctl status jelu 回显,见 4.9 节),1GB 内存的服务器可稳定运行;
  • 磁盘:jar 本体约 103MB;若安装 calibre(可选,元数据抓取用)连同依赖约占 1GB;数据库为 SQLite 单文件,实测录入少量书目时 jelu.db 仅 278KB,随书目量增长。

2.3 网络与下载渠道

⚠️ 网络说明(实测):Jelu 官方发布渠道只有 GitHub Releases(github.com/bayang/jelu/releases),无国内官方镜像站。查询日期 2026-10-28 实测直连 GitHub 下载该 jar 限速严重(约 2 分钟仅完成 1.9MB),最终经 HTTP 代理完成完整下载;部署环境若有可用代理,用 export http_proxy=http://代理主机:端口 https_proxy=http://代理主机:端口 会话级注入或 curl -x http://代理主机:端口 -Lo jelu-0.88.2.jar <同一 URL> 单次指定。无论走哪个渠道,务必以 4.4 节的 sha256 校验收口。

2.4 可选组件

  • calibre(推荐):提供 fetch-ebook-metadata 命令行工具,是 Jelu 默认的元数据抓取提供方:
sudo apt-get install -y calibre   # 连同依赖体积较大,耐心等待
which fetch-ebook-metadata

实测回显:

/usr/bin/fetch-ebook-metadata
  • nginx(可选):反向代理与后续加 HTTPS 的入口,安装与配置见 7.3 节。

三、版本选型说明

Jelu 发布节奏较快,查询日期 2026-10-28 实查 GitHub Releases 的近期版本如下:

版本发布日期要点
v0.87.42026-10-05常规修复
v0.87.52026-10-05常规修复
v0.88.02026-10-05SQLite 引入 journalMode 配置(delete/wal),调优 busy_timeout 与连接池
v0.88.12026-10-05修复封面抓取在写事务外执行
v0.88.22026-10-07修复添加书目页 CSS、Goodreads ISBN 格式不一致、设置进度时更新事件

选型结论:新装直接用最新稳定版 v0.88.2(查询日期 2026-10-28 时 GitHub Releases 的 latest 即 v0.88.2)。本文全部步骤以 v0.88.2 为准,安装资产与校验值:

  • 资产:jelu-0.88.2.jar,108,216,402 字节;
  • sha256:1d063355fbde5e75dcc67f1a700cc113aa3f3f00b80e69b299c9cd682d4cb085(实测 sha256sum 与发布页摘要一致,见 4.4 节)。

⚠️ 注意:0.88.x 三天连发 5 版,节奏较快;升级前先备份数据库(7.4 节),升级与回滚流程见第八章。

四、方案 A:官方 jar 在线安装 + systemd 托管(推荐)

4.1 方案说明

官方 Releases 直接发布 Spring Boot fat jar,单进程 java -jar 即可运行,是官方文档推荐、也是自托管服务器最常用的形态。本章流程:安装 Java → 创建运行用户与目录 → 下载并校验 jar → 前台试跑(Liquibase 自动建库)→ 写外置配置 → 编写 systemd 单元 → 启动自启 → 验证 → 创建管理员。

4.2 安装 Java 运行时

若第二章 2.1 节已完成可跳过本步,直接用 java -version 确认输出为 openjdk version "21.0.12.1" 即可继续。

4.3 创建运行用户与目录

Jelu 以专用系统用户运行,不可登录、只拥有自己的数据目录:

sudo useradd -r -d /var/lib/jelu -s /usr/sbin/nologin jelu
sudo mkdir -p /opt/jelu /var/lib/jelu
sudo chown jelu:jelu /opt/jelu /var/lib/jelu

验证用户与家目录:

id jelu
getent passwd jelu

实测回显:

uid=997(jelu) gid=997(jelu) groups=997(jelu)
jelu:x:997:997::/var/lib/jelu:/usr/sbin/nologin

说明:useradd -d /var/lib/jelu 指定的家目录有两个作用——Jelu 内部默认把数据库放在 ${user.home}/.jelu/database/ 下,且 systemd 单元的 WorkingDirectory 也将指向这里(4.7 节)。

4.4 下载 jar 并校验

cd /opt/jelu
sudo wget https://github.com/bayang/jelu/releases/download/v0.88.2/jelu-0.88.2.jar

⚠️ 网络说明(实测):见 2.3 节。直连限速不可用时改走 HTTP 代理(curl -x http://代理主机:端口 -Lo jelu-0.88.2.jar https://github.com/bayang/jelu/releases/download/v0.88.2/jelu-0.88.2.jar),本文实测环境即经代理完成下载。

下载完成后做大小与 sha256 双重校验:

sudo ls -l jelu-0.88.2.jar
sudo sha256sum jelu-0.88.2.jar

实测回显:

-rw-r--r-- 1 root root 108216402 Oct 28 08:11 jelu-0.88.2.jar
1d063355fbde5e75dcc67f1a700cc113aa3f3f00b80e69b299c9cd682d4cb085  jelu-0.88.2.jar

sha256 与发布页摘要一致、字节数与发布页一致,方可进入下一步。

4.5 首次前台试跑(Liquibase 自动建库实录)

正式托管前先前台跑一次,确认应用能起来、数据库能初始化:

cd /var/lib/jelu
sudo -u jelu java -jar /opt/jelu/jelu-0.88.2.jar

首次启动时 Jelu 通过 Liquibase 自动建库,实测日志(输出节选,首次建库):

2026-10-28T08:15:23.360Z  INFO 3908 --- [main] liquibase.lockservice                    : Successfully acquired change log lock
2026-10-28T08:15:23.586Z  INFO 3908 --- [main] liquibase.ui                             : Liquibase: Update has been successful. Rows affected: 56
2026-10-28T08:15:23.588Z  INFO 3908 --- [main] liquibase.lockservice                    : Successfully released change log lock

Rows affected: 56 表示 56 个变更集全部执行成功。随后启动完成的日志(输出节选,同环境已建库后的启动):

2026-10-28T09:18:13.364Z  INFO 5204 --- [main] i.g.b.jelu.config.DatabasePoolConfig     : Database pool size of 1 with journal mode DELETE
2026-10-28T09:18:17.359Z  INFO 5204 --- [main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 11111 (http) with context path '/'
2026-10-28T09:18:17.557Z  INFO 5204 --- [main] i.github.bayang.jelu.JeluApplicationKt   : Started JeluApplicationKt in 6.661 seconds (process running for 7.159)

此刻默认库已落在 ${user.home}/.jelu/database/jelu.db(本环境即 /var/lib/jelu/.jelu/database/jelu.db),日志在 ~/.jelu/database/jelu.log。确认能启动后按 Ctrl-C 停止,进入下一步把数据路径整理到规范位置。

4.6 写入外置配置文件

Jelu 内置一份默认配置(jar 内 BOOT-INF/classes/application.yml);Spring Boot 约定进程工作目录下的 application.yml 会自动覆盖内置默认值。利用这一点,在工作目录放一份外置配置,把数据库、图片、导入目录统一收拢到 /var/lib/jelu/:

sudo tee /var/lib/jelu/application.yml > /dev/null <<'EOF'
jelu:
  database:
    path: /var/lib/jelu/
  files:
    images: /var/lib/jelu/files/images/
    imports: /var/lib/jelu/files/imports/
  metadata:
    calibre:
      path: /usr/bin/fetch-ebook-metadata
  cors:
    allowed-origins:
      - "*"

logging:
  level:
    org.springframework.security: DEBUG
EOF
sudo chown jelu:jelu /var/lib/jelu/application.yml

逐项说明:

  • jelu.database.path: /var/lib/jelu/:数据库文件、日志文件的根路径,覆盖后库文件为 /var/lib/jelu/jelu.db、日志为 /var/lib/jelu/jelu.log(不再藏在 .jelu 子目录);
  • jelu.files.images / jelu.files.imports:封面与作者图片落盘目录、CSV/ISBN 导入文件目录;
  • jelu.metadata.calibre.path: /usr/bin/fetch-ebook-metadata:元数据抓取工具路径(2.4 节安装 calibre 后的实际位置);未装 calibre 时删除该组配置并依赖插件提供方;
  • jelu.cors.allowed-origins: "*":默认放行所有来源,若只经同域反代访问可收紧为具体域名;
  • logging.level.org.springframework.security: DEBUG:安全认证链路调试日志,排障期开启,稳定后可改为 INFO。

验证外置配置生效:重启试跑后日志应出现 started by jelu in /var/lib/jelu,且数据目录直下出现库文件。本环境实测:

sudo ls -l /var/lib/jelu/jelu.db
-rw-r--r-- 1 jelu jelu 278528 Oct 28 09:24 jelu.db

4.7 编写 systemd 单元

创建 /etc/systemd/system/jelu.service(内容与实测环境一致):

sudo tee /etc/systemd/system/jelu.service > /dev/null <<'EOF'
[Unit]
Description=Jelu self-hosted reading tracker
After=network.target

[Service]
Type=simple
User=jelu
Group=jelu
WorkingDirectory=/var/lib/jelu
ExecStart=/usr/bin/java -Xms256m -Xmx512m -Djava.io.tmpdir=/var/lib/jelu/tmp -jar /opt/jelu/jelu-0.88.2.jar
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

逐项说明:

  • User/Group=jelu:以 4.3 节的专用低权用户运行,不使用 root;
  • WorkingDirectory=/var/lib/jelu:必须是 jelu 用户可写的专属目录。Jelu 会在工作目录下生成 Lucene 全文搜索索引(write.lock、segments_2、_0.cfe 等文件),工作目录不可写或残留锁文件会导致启动失败(排障实录见 9.2 节);
  • ExecStart 的 JVM 参数:
    • -Xms256m -Xmx512m:堆内存限定在 256–512MB 区间,个人/家庭自托管足够;
    • -Djava.io.tmpdir=/var/lib/jelu/tmp:把 Java 临时目录指到数据目录下。这一项很关键——SQLite JDBC 驱动启动时会把原生库 .so 解压到 java.io.tmpdir(默认 /tmp)再加载,若服务器 /tmp 以 noexec 挂载(安全加固常态)会加载失败、整个应用起不来(排障实录见 9.1 节);
  • Restart=on-failure + RestartSec=5:异常退出 5 秒后自动拉起。

创建临时目录并赋权:

sudo mkdir -p /var/lib/jelu/tmp
sudo chown jelu:jelu /var/lib/jelu/tmp

4.8 启动与开机自启

sudo systemctl daemon-reload
sudo systemctl enable jelu
sudo systemctl start jelu

三条命令各自执行:daemon-reload 让 systemd 读取新单元文件;enable 注册开机自启;start 立即启动。

4.9 验证服务

逐条执行并核对回显:

systemctl is-active jelu
active
systemctl status jelu --no-pager -l

实测回显(输出节选):

● jelu.service - Jelu self-hosted reading tracker
     Loaded: loaded (/etc/systemd/system/jelu.service; enabled; preset: enabled)
     Active: active (running) since Wed 2026-10-28 09:19:36 UTC; 1h 18min ago
   Main PID: 5300 (java)
      Tasks: 43 (limit: 2932)
     Memory: 477.2M (peak: 478.2M)
        CPU: 22.628s
     CGroup: /system.slice/jelu.service
             └─5300 /usr/bin/java -Xms256m -Xmx512m -Djava.io.tmpdir=/var/lib/jelu/tmp -jar /opt/jelu/jelu-0.88.2.jar

Memory: 477.2M 即 512MB 堆下的真实常驻内存,CGroup 行可直接核对 JVM 参数已生效。再用进程表复核一遍 JVM 参数(等效 jps -v 的验证手段):

ps -ef | grep '[j]ava'
jelu       5300       1  1 09:19 ?        00:00:22 /usr/bin/java -Xms256m -Xmx512m -Djava.io.tmpdir=/var/lib/jelu/tmp -jar /opt/jelu/jelu-0.88.2.jar

端口与 HTTP 接口验证:

ss -tlnp | grep 11111
LISTEN 0      100                *:11111            *:*    users:(("java",pid=5300,fd=11))
curl -s http://127.0.0.1:11111/api/v1/server-settings
{"metadataFetchEnabled":true,"metadataFetchCalibreEnabled":true,"appVersion":"0.88.2","ldapEnabled":false,"metadataPlugins":[{"name":"calibre","order":1000}]}

appVersion: "0.88.2" 与所装 jar 版本一致,metadataPlugins 里出现 calibre 说明 4.6 节的元数据配置已加载。

4.10 创建管理员账户

服务就绪后,浏览器访问 http://<服务器IP>:11111 进入初始化页面,第一个注册的用户自动成为管理员(官方文档 First login 说明)。命令行方式同样可行(实测流程):先取会话 Cookie 与 CSRF 令牌,再提交创建请求:

# 1. 取会话与 CSRF 令牌(token 同时出现在 Cookie 与响应体中)
curl -s -c /tmp/cj http://127.0.0.1:11111/api/v1/token
{"token":"1fdea51c-f746-4d25-99dd-c91ed6c6a746"}
# 2. 携带 Cookie 与 X-XSRF-TOKEN 头创建首个管理员(示例演示账号 admin/q1w2e3r4)
TOK=$(python3 -c "import json;print(json.load(open('/dev/stdin')).get('token',''))" < <(curl -s -c /tmp/cj2 http://127.0.0.1:11111/api/v1/token))
curl -s -w '\n=> HTTP %{http_code}\n' -b /tmp/cj2 -X POST http://127.0.0.1:11111/api/v1/users \
  -H 'Content-Type: application/json' -H "X-XSRF-TOKEN: $TOK" \
  -d '{"login":"admin","password":"q1w2e3r4","email":"","isAdmin":true}'

实测回显(响应体同形、HTTP 200;id 为平台生成的 UUID,每次不同):

{"id":"8ab5893d-6f33-3ebd-0a80-539927125ff7","creationDate":"2026-10-28T09:16:35Z","login":"admin","password":"****","modificationDate":"2026-10-28T09:16:35Z","admin":true,"provider":"JELU_DB"}
=> HTTP 200

创建后初始化状态随之关闭(isInitialSetup 由 true 变 false,实测回显):

curl -s http://127.0.0.1:11111/api/v1/setup/status
{"isInitialSetup":false}

用 Basic 认证验证管理员可登录:

curl -s -u admin:q1w2e3r4 http://127.0.0.1:11111/api/v1/users/me
{"user":{"id":"8ab5893d-6f33-3ebd-0a80-539927125ff7","creationDate":null,"login":"admin","password":"****","modificationDate":null,"admin":true,"provider":"JELU_DB"},"token":"9b93e72a-b757-4654-92f8-335410448463"}

至此浏览器以 admin / q1w2e3r4 登录即可开始录入书目。

4.11 防火墙放行(按需)

直连 11111 端口访问时放行;若只走 7.3 节的 nginx 反代(80/443),则只放行反代端口、不对公网暴露 11111。以 ufw 为例(⚠️ 需要已安装并启用 ufw 的真机环境;本文实测环境未启用 ufw,命令为标准用法):

sudo ufw allow 11111/tcp comment 'Jelu'

五、方案 B:离线安装(断网内网)

5.1 方案说明

目标服务器完全断网时,用一台同版本同架构(Ubuntu 24.04 amd64)的在线机准备全部物料(jar、Java 及其依赖的 deb 包),经移动介质拷入内网安装。Jelu 本体只是「一个 jar + 一个 systemd 单元」,离线安装没有任何额外组件。

5.2 在线机准备物料

物料一:jar 与校验值(同 4.4 节,sha256sum 输出必须一致)。

物料二:Java 运行时及其全部依赖 deb 包。用 --download-only 让 apt 把 openjdk-21-jre-headless 连同依赖一起下载到本地缓存目录(不安装):

sudo apt-get install --download-only -y openjdk-21-jre-headless
ls /var/cache/apt/archives/*.deb

若只需单独验证主包,apt-get download 的实测回显如下:

Get:1 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 openjdk-21-jre-headless amd64 21.0.12.1+1-1~24.04.4 [46.5 MB]
Fetched 46.5 MB in 3s (13.7 MB/s)
-rw-r--r-- 1 root root 46499662 Sep 21 16:30 openjdk-21-jre-headless_21.0.12.1+1-1~24.04.4_amd64.deb

把 /var/cache/apt/archives/ 下的全部 deb 与 jar 一起打包拷贝:

tar czf jelu-offline.tar.gz -C /var/cache/apt/archives . -C / jelu-offline-jar/jelu-0.88.2.jar

💡 技术拓展:离线装 deb 的两种常见方式——dpkg -i *.deb(把依赖包与主包放同一目录一次性安装,适合完全断网)、apt-get install -f(联网修复缺失依赖,断网不可用)。内网多台部署时也可自建 apt 源(dpkg-dev 的 dpkg-scanpackages),本文按单机 dpkg -i 演示。

5.3 内网机安装 Java

sudo dpkg -i /path/to/debs/*.deb
java -version

全部 deb 在同一目录一次安装时,依赖关系可一次满足;java -version 输出与 2.1 节一致即成功。

5.4 内网机部署 jar 与 systemd

jar 拷贝到 /opt/jelu/jelu-0.88.2.jar 后,完全复用 4.3–4.8 节的命令与文件:创建运行用户与目录(4.3)、写入 /var/lib/jelu/application.yml(4.6,原样照抄)、创建临时目录(4.7)、写入 /etc/systemd/system/jelu.service(4.7,原样照抄),然后:

sudo systemctl daemon-reload
sudo systemctl enable jelu
sudo systemctl start jelu

5.5 验证

与 4.9 节完全一致:systemctl is-active jelu 返回 active,curl -s http://127.0.0.1:11111/api/v1/server-settings 返回含 appVersion 的 JSON,即离线安装完成。唯一差异:断网环境下 fetch-ebook-metadata 无法在线抓取元数据,录入书目只能手动填写(联网后再补抓)。

六、方案对比与选择建议

维度方案 A:在线安装(官方 jar + systemd)方案 B:离线安装
是否需联网需要(GitHub Releases 下载 jar)仅在线机需要,目标机全程断网
Java 获取apt 官方源直接装--download-only 打包 deb 拷入后 dpkg -i
jar 获取wget/curl 直下或经代理外网机下载后 sha256 校验再拷入
版本更新直接下新 jar 替换每次升级都要重新走物料打包
适用场景能出网的云主机/家用服务器内网隔离的家庭/企业 NAS

选择建议:目标机可出网选方案 A(本文主线,步骤最短);内网隔离环境选方案 B,且把 jar 的 sha256 校验当作强制步骤,防止介质传递过程中文件损坏。

七、配置说明

7.1 配置文件与加载机制

Jelu 的配置有三层覆盖关系:jar 内置默认值 → 工作目录 application.yml 覆盖 → 命令行 --参数 覆盖。本文 4.6 节用的是第二层(推荐:随 systemd 单元的 WorkingDirectory 生效,改完 systemctl restart jelu 即生效)。查看内置默认值的命令:

unzip -p /opt/jelu/jelu-0.88.2.jar BOOT-INF/classes/application.yml

全部参数(数据库 journalMode、会话时长、日志滚动、认证方式、元数据提供方等)的逐项实测详解见《Jelu 0.88.2 配置参数详解》。

7.2 JVM 内存调整

个人/家庭自托管场景把堆限定在 256–512MB 即可。调整位置就是 4.7 节单元文件里的 ExecStart 行:把 -Xms256m -Xmx512m 改为目标值(如 -Xms256m -Xmx256m 进一步压缩到 256MB 堆),随后重载并重启:

sudo systemctl daemon-reload
sudo systemctl restart jelu
ps -ef | grep '[j]ava'

ps -ef 输出中出现新的 -Xms… -Xmx… 取值即生效(本文实测生效回显见 4.9 节);也可用 systemctl status jelu 的 Memory: 行观察常驻内存变化。

7.3 nginx 反向代理(可选)

Jelu 的 Web UI 与 API 同端口,反代只需转发一个 upstream。创建 /etc/nginx/sites-available/jelu.conf:

server {
    listen 80;
    server_name _;
    location / {
        proxy_pass http://127.0.0.1:11111;
        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;
    }
}

启用并重载(本文实测环境即此配置,监听 80 转发 11111):

sudo ln -s /etc/nginx/sites-available/jelu.conf /etc/nginx/sites-enabled/jelu.conf
sudo nginx -t
sudo systemctl reload nginx

nginx -t 通过后 reload 生效,浏览器走 80 端口访问即可。若后续配置 HTTPS,把 proxy_set_header X-Forwarded-Proto $scheme; 中的 $scheme 固定为 https,否则 Jelu 生成的绝对链接会退回 http。

7.4 数据备份要点

Jelu 全部业务数据在 SQLite 单文件 jelu.db(外加封面图片目录 files/images/)。最稳妥的备份是停服后整目录拷贝:

sudo systemctl stop jelu
sudo tar czf /var/backup/jelu-$(date +%F).tar.gz -C /var/lib/jelu jelu.db application.yml files
sudo systemctl start jelu

若数据库启用 wal 模式(见《Jelu 0.88.2 配置参数详解》),热备份必须连 jelu.db-wal、jelu.db-shm 一起拷贝或先停服;本文默认 delete 模式下 jelu.db 单文件即可。

八、升级与回滚

8.1 升级步骤(多版本并存)

Jelu 升级就是「换 jar + 重启」,数据库表结构由 Liquibase 在启动时自动迁移(4.5 节实测的 Rows affected: 56 即该机制):

  1. 备份数据(7.4 节命令);
  2. 下载新版本 jar 到 /opt/jelu/(如 jelu-0.89.0.jar),与旧版并存、sha256sum 校验;
  3. 修改 /etc/systemd/system/jelu.service 的 ExecStart 行,把 jar 文件名指到新版本;
  4. sudo systemctl daemon-reload 后 sudo systemctl restart jelu;
  5. 验证:curl -s http://127.0.0.1:11111/api/v1/server-settings 中的 appVersion 已变为新版本,日志中出现 Liquibase 增量迁移(有表结构变更时)与 Started JeluApplicationKt;
  6. 观察一两天稳定后,删除旧 jar 释放 103MB 空间。

8.2 回滚

新版本启动异常时回滚:停服 → 恢复升级前的 jelu.db 备份 → 把 ExecStart 行改回旧 jar 文件名 → daemon-reload + start。

⚠️ 注意:Liquibase 迁移只向前兼容——新版本对库做过表结构变更后,直接换回旧 jar 可能因表结构不匹配而启动失败,所以回滚必须连同 8.1 步骤 1 的数据库备份一起恢复,这也是「升级前必备份」的原因。

九、常见问题(FAQ)

Q1:启动失败,日志报 Failed to load native library: sqlite-…-libsqlitejdbc.so 与 failed to map segment from shared object,随后 Application run failed

这是本文实测遇到的第一个真实故障。完整排障过程:

  1. 现象(/var/lib/jelu/jelu.log 实测节选):
2026-10-28T08:14:36.152Z ERROR 3861 --- [main] org.sqlite.SQLiteJDBCLoader              : Failed to load native library: sqlite-3.53.2.0-778b9383-b279-4f74-918f-08d0243fd87a-libsqlitejdbc.so. osinfo: Linux/x86_64

java.lang.UnsatisfiedLinkError: /tmp/sqlite-3.53.2.0-778b9383-b279-4f74-918f-08d0243fd87a-libsqlitejdbc.so: /tmp/sqlite-3.53.2.0-778b9383-b279-4f74-918f-08d0243fd87a-libsqlitejdbc.so: failed to map segment from shared object
  1. 定位:SQLite JDBC 驱动(sqlite-jdbc)启动时把原生库 .so 解压到 java.io.tmpdir(默认 /tmp)再 dlopen 加载;failed to map segment 是典型的 noexec 限制。实测确认挂载参数:
findmnt -no OPTIONS /tmp
rw,nosuid,nodev,noexec,relatime,inode64
  1. 解决:把 Java 临时目录指到可执行的数据目录(4.7 节单元文件中的 -Djava.io.tmpdir=/var/lib/jelu/tmp),重启后日志出现 Database pool size of 1 with journal mode DELETE 且 Started JeluApplicationKt 正常,故障消除。

Q2:启动失败,报 NoSuchFileException: <工作目录>/write.lock 或 AccessDeniedException: <工作目录>/write.lock

Jelu 用 Lucene 维护书名/作者的全文搜索索引,索引文件(write.lock、segments_2、_0.cfe 等)直接生成在进程工作目录下。本文实测在把 WorkingDirectory 指到 jar 同目录(/opt/jelu)时触发过该异常(异常链实测节选:Caused by: java.nio.file.NoSuchFileException: /opt/jelu/write.lock,并伴随 Suppressed: java.nio.file.AccessDeniedException: /opt/jelu/write.lock)。处置:

  • systemd 单元的 WorkingDirectory 必须指向 jelu 用户可写的专属目录(本文采用 /var/lib/jelu,见 4.7 节);
  • 若是上一次异常退出残留的锁文件,确认无残留 java 进程(ps -ef | grep '[j]ava')后删除工作目录下的 write.lock 再启动。

本文实测:把工作目录调整为 /var/lib/jelu 并清理残留后,连续重启多次未再复现。

Q3:每次启动日志都有 ERROR「The use of configuration keys that are no longer supported … spring.mvc.throw-exception-if-no-handler-found」

0.88.2 内置默认配置里的 spring.mvc.throw-exception-if-no-handler-found 键在 Spring Boot 4 运行时下已废弃,每次启动都会打印这段 PropertiesMigrationListener 报告(实测回显节选):

The use of configuration keys that are no longer supported was found in the environment:

Property source 'Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'':
	Key: spring.mvc.throw-exception-if-no-handler-found
		Line: 25
		Reason: DispatcherServlet property is deprecated for removal and should no longer need to be configured.

实测结论:成功启动的实例日志中该 ERROR 持续出现但服务运行正常(本文实测实例连续运行处理请求无异常),属于上游内置配置与新运行时的兼容性提示;只有当它与其它启动期异常叠加时才可能中断启动,此时按完整日志先解决其它根因。

Q4:Web server failed to start. Port 11111 was already in use.

端口被占。先找占用者:ss -tlnp | grep 11111;若需要临时拉起第二个实例做验证,可换端口与独立库路径前台启动(实测回显):

java -Djava.io.tmpdir=/var/lib/jelu/tmp -jar /opt/jelu/jelu-0.88.2.jar --server.port=11112 --jelu.database.path=/tmp/jelu-p12/
2026-10-28T11:01:24.674Z  INFO 5960 --- [main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 11112 (http) with context path '/'

正式改端口则修改配置文件中的 server.port(重启生效)。⚠️ 生产环境不要长期用 /tmp 做数据路径,此处仅为端口验证演示。

Q5:中文 ISBN 在线抓取不到元数据

Jelu 默认经 calibre 的 fetch-ebook-metadata 抓取,其检索源以 Google Books、Amazon 为主,对国内出版社的 ISBN 覆盖差。实测(ISBN 9787532776413,输出节选):

Using plugins: Google (1, 1, 1), Amazon.com (1, 3, 7)
****************************** Google (1, 1, 1) ******************************
Found 0 results
****************************** Amazon.com (1, 3, 7) ******************************
Found 0 results

处置:改用「题名 + 作者」搜索(匹配英文书名时命中率高);或按官方配置文档部署 google/inventaire.io 等插件提供方补充源;都失败时可手动填写书目信息录入,不影响后续追踪功能。

十、卸载与清理

sudo systemctl disable --now jelu
sudo rm /etc/systemd/system/jelu.service
sudo systemctl daemon-reload
sudo rm -rf /opt/jelu /var/lib/jelu
sudo userdel jelu

逐条说明:disable --now 停止服务并取消开机自启;删除单元文件后重载 systemd;删除 jar 与全部数据(含数据库,删除前确认备份已另存);删除运行用户。配置过 7.3 节反代的再删除站点文件并重载 nginx(每行一个单元):

sudo rm /etc/nginx/sites-enabled/jelu.conf
sudo systemctl reload nginx

验证清理结果:systemctl status jelu 提示单元不存在、ls /opt/jelu /var/lib/jelu 提示 No such file or directory、getent passwd jelu 无输出。

十一、参考链接