Ubuntu 22.04 安装 Family Accounting Tool v4.29 指南
在 Ubuntu 22.04 上从零安装 Family Accounting Tool v4.29 的完整指南:依赖链、官方发行包在线/离线方案、MariaDB 准备、初始化、systemd/SysV 托管、JVM 堆限制与排障实录。
Ubuntu 22.04 安装 Family Accounting Tool v4.29 指南
目标:在 Ubuntu 22.04 服务器上从零安装并运行开源家庭记账系统 Family Accounting Tool(仓库名 facto)v4.29,覆盖依赖链、官方发行包在线/离线两种安装方案、数据库准备、初始化、systemd/SysV 服务托管、JVM 堆限制、验证与排障实录。本文全部命令与回显均在 Ubuntu 22.04.5 LTS(Java 11.0.32.1、MariaDB 10.6.23)上实测通过。
一、环境信息
本文实测环境如下,读者在同等环境下可完全复现:
- 操作系统:Ubuntu 22.04.5 LTS(
PRETTY_NAME实测值;架构 x86_64) - 应用:Family Accounting Tool v4.29(官方发行包
release-v4.29.tar.gz,约 84 MB) - 运行时:OpenJDK 11(
openjdk-11-jdk-headless,实测版本 11.0.32.1,Ubuntu 22.04 官方仓库源) - 数据库:MariaDB 10.6.23(
mariadb-server,Ubuntu 22.04 官方仓库源,实测版本号10.6.23-MariaDB-0ubuntu0.22.04.1) - 安装目录:
/opt/facto(应用本体)、/opt/facto/attachments(附件目录) - 监听端口:9000(Play Framework 默认 HTTP 端口)
- 数据库账号:数据库用户
facto、密码q1w2e3r4,库名facto;应用管理员admin、密码q1w2e3r4(均为本文演示值) - 官方仓库:
https://github.com/nymanjens/facto(v4.29 为查询日 2026-10-23 时 GitHub Releases 标记的 Latest 稳定版)
💡 说明:Family Accounting Tool 的官方 README 推荐方式是「下载发行包 + 本机 MariaDB」,即本文方案 A;官方同时存在 Docker 形态,但作者在 README 中标注为 discouraged(仅建议体验用,生产持久化建议走发行包路线,理由见仓库 issue #4)。
二、前置条件
开始安装前,逐项确认以下条件,避免中途卡住:
- 一台 Ubuntu 22.04 服务器(物理机或虚拟机均可),拥有 root 或 sudo 权限;
- 服务器可访问 GitHub Releases(方案 A 需要;内网隔离环境直接走方案 B);
- 磁盘剩余空间 ≥ 1 GB(发行包 84 MB + 解压后约 240 MB + 数据库与附件增长空间);
- 端口 9000 未被占用(验证命令:
ss -ltn | grep 9000,无输出即空闲); - 服务器语言环境为 UTF-8(⚠️ 关键前提,非 UTF-8 环境会触发启动失败,排查过程见「九、常见问题」FAQ-1)。
验证基础条件是否满足:
uname -m # 确认架构
ss -ltn | grep 9000 # 确认端口未占用(无输出=空闲)
实测输出(空闲状态下第二条无回显):
x86_64
三、版本选型说明
Family Accounting Tool 的版本号从仓库 Releases 页获取。查询日 2026-10-23 的版本事实如下(来源:GitHub Releases 页与 releases.atom):
| 版本 | 发布时间 | 二进制产物 | 适用结论 |
|---|---|---|---|
| v4.29 | 2026-08-01 | 有(release-v4.29.tar.gz,GitHub 标记 Latest) | 本文安装目标:最新可安装稳定版 |
| v4.30 | 2026-08-29 | 无(仅源码标签,无编译产物) | 不可直接安装,仅用于源码构建 |
关键差异解读:该项目的 Release 产物由 CI 编译后挂到对应标签下;只有带 release-v<版本>.tar.gz 资产的标签才是可直接安装的稳定版。选型时认准 Releases 页的「Latest」标记即可。Java 运行时按官方 README 要求使用 JDK 11(Play 2.8.20 框架的官方支持范围),Ubuntu 22.04 仓库源中的 openjdk-11-jdk-headless 正好匹配,无需额外添加 PPA。
四、方案 A:官方发行包在线安装(推荐)
4.1 方案说明
本方案是官方 README 的标准路线:从 GitHub Releases 下载已编译的发行包(内含 bin/server 启动脚本与全部依赖 jar),配合本机 MariaDB 数据库运行。优点是不需要 Scala/Node 构建工具链、步骤最少;缺点是下载依赖外网连通性(84 MB)。
4.2 安装基础依赖
一键安装 JDK 11、MariaDB 与下载工具:
apt-get update
apt-get install -y openjdk-11-jdk-headless mariadb-server wget curl locales
实测输出节选(依赖解析行较多,此处为首尾节选):
Reading package lists... Done
Building dependency tree... Done
...
Setting up openjdk-11-jdk-headless:amd64 (11.0.32.1+1-1ubuntu1~22.04) ...
...
Processing triggers for man-db (2.10.2-1) ...
验证 Java 版本:
java -version
实测输出:
openjdk version "11.0.32.1" 2026-08-18
OpenJDK Runtime Environment (build 11.0.32.1+1-post-1ubuntu1-22.04-Ubuntu)
4.3 准备 UTF-8 语言环境(必做)
Family Accounting Tool 的 YAML 配置文件(conf/accounting-config.yml)包含非 ASCII 字符,应用按 JVM 默认字符集读取该文件;若系统语言环境为 POSIX/ANSI,启动时将直接报错(真实排障过程见 FAQ-1)。先确认并准备 UTF-8 环境:
locale | head -2
实测输出(Ubuntu 22.04 桌面版/云镜像通常直接是 en_US.UTF-8,可跳过本节后续命令;最小化安装/精简系统常见如下空值输出):
LANG=
LANGUAGE=
若 LANG 为空,生成并启用 en_US.UTF-8:
sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8
实测输出(节选):
Generating locales (this might take a while)...
en_US.UTF-8... done
Generation complete.
⚠️ 注意:update-locale 写入 /etc/default/locale,对下一次登录会话生效。当前会话内立即生效需执行 export LANG=en_US.UTF-8,或重新登录后再继续。
4.4 部署 MariaDB 数据库
启动数据库并创建应用专用的库与账号(示例密码 q1w2e3r4,生产环境请替换为强口令):
service mariadb start
实测输出:
* Starting MariaDB database server mariadbd
...done.
mysql -e "SELECT VERSION() AS version;"
实测输出:
version
10.6.23-MariaDB-0ubuntu0.22.04.1
创建空库与专用账号:
mysql -e "CREATE DATABASE facto CHARACTER SET utf8mb4;"
mysql -e "CREATE USER 'facto'@'localhost' IDENTIFIED BY 'q1w2e3r4';"
mysql -e "GRANT ALL PRIVILEGES ON facto.* TO 'facto'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
四条命令均无回显即成功。验证账号可用:
mysql -ufacto -pq1w2e3r4 facto -e "SHOW DATABASES;"
实测输出:
+--------------------+
| Database |
+--------------------+
| facto |
| information_schema |
+--------------------+
4.5 下载并解压发行包
创建安装目录并下载官方发行包:
mkdir -p /opt/facto
wget https://github.com/nymanjens/facto/releases/download/v4.29/release-v4.29.tar.gz
实测输出(进度条省略,收尾信息如下):
release-v4.29.tar.gz 100% [============================================>] 80.04M 9.2MB/s in 9.9s
2026-10-23 11:22:30 (8.1 MB/s) - 'release-v4.29.tar.gz' saved [83932089/83932089]
校验完整性(sha256 值以官方资产页公布值为准,本文实测值如下):
sha256sum release-v4.29.tar.gz
实测输出:
3681d703817ae2a3a80125de270e1bf73436f4474e32a3a7593a80b2e28925a7 release-v4.29.tar.gz
解压到安装目录。⚠️ 注意:该压缩包没有顶层目录,bin/、conf/、lib/ 会直接铺在当前目录,必须先进入 /opt/facto 再解压:
cd /opt/facto
tar -xzf /root/release-v4.29.tar.gz
ls -la
实测输出:
total 28
drwxr-xr-x 5 root root 4096 Oct 23 11:22 .
drwxr-xr-x 5 root root 4096 Oct 23 11:23 ..
drwxrwxr-x 2 1000 1000 4096 Aug 1 14:56 bin
drwxrwxr-x 2 1000 1000 4096 Aug 1 14:56 conf
drwxrwxr-x 2 1000 1000 12288 Aug 1 14:56 lib
💡 说明:
bin/内是启动脚本(server与 Windows 用的server.bat),lib/内是全部依赖 jar(Play 2.8.20、Slick 3.3.0 等),conf/内是配置文件与语言包(messages、messages.nl)。
4.6 编写基础配置
编辑 /opt/facto/conf/application.conf,发行包内的原始内容为:
# Play Framework
play {
# Secret key
http.secret.key="changeme"
# The application languages
i18n.langs = ["en"] # Supported: "en" and "nl"
# Maximal attachment size
server.websocket.frame.maxLength = 100M
# Session configuration
http.session.maxAge = 60d
}
# Database configuration
db.default {
driver = com.mysql.jdbc.Driver
url = "jdbc:mysql://localhost/facto?user=mysqluser&password=mysqlpassword"
slick.profile = "slick.jdbc.MySQLProfile$"
}
# Application-specific configuration
app {
accounting.configYamlFilePath = "conf/accounting-config.yml"
accounting.attachmentsFolder = "/changeme/path/to/attachments/folder"
setup.defaultPassword = "changeme"
}
需要修改四处(逐项说明):
| 配置项 | 修改为 | 作用 |
|---|---|---|
play.http.secret.key | "facto-secret-2026-q1w2e3r4" | Play 会话签名密钥,同时兼作外部 API 的 appSecret,必须改成足够长的随机串 |
db.default.url | "jdbc:mysql://localhost/facto?user=facto&password=q1w2e3r4" | 指向 4.4 节创建的库与账号 |
app.accounting.attachmentsFolder | "/opt/facto/attachments" | 附件落盘目录,原值 /changeme/... 是占位符不改会出错 |
app.setup.defaultPassword | "q1w2e3r4" | 初始 admin 用户的密码(仅首次 -DcreateAdminUser 时使用) |
创建附件目录并写入修改后的配置:
mkdir -p /opt/facto/attachments
修改后的 conf/application.conf 关键段落(与原文件逐字对照,仅上述四处不同):
# Play Framework
play {
# Secret key
http.secret.key="facto-secret-2026-q1w2e3r4"
# The application languages
i18n.langs = ["en"] # Supported: "en" and "nl"
# Maximal attachment size
server.websocket.frame.maxLength = 100M
# Session configuration
http.session.maxAge = 60d
}
# Database configuration
db.default {
driver = com.mysql.jdbc.Driver
url = "jdbc:mysql://localhost/facto?user=facto&password=q1w2e3r4"
slick.profile = "slick.jdbc.MySQLProfile$"
}
# Application-specific configuration
app {
accounting.configYamlFilePath = "conf/accounting-config.yml"
accounting.attachmentsFolder = "/opt/facto/attachments"
setup.defaultPassword = "q1w2e3r4"
}
💡 说明:
i18n.langs = ["en"]支持值只有"en"与"nl"(英/荷),无中文语言包;http.session.maxAge = 60d控制登录会话有效期(实测 60 天)。全部配置项的默认值、取值约束与实测行为另见 《Family Accounting Tool v4.29 配置参数详解》。
4.7 初始化数据库表与管理员
官方启动脚本 bin/server 通过 JVM 系统属性执行一次性初始化动作,按顺序执行两条命令(均以 UTF-8 语言环境运行):
export LANG=en_US.UTF-8
cd /opt/facto
bin/server -DdropAndCreateNewDb
实测输出(首尾节选;WARNING 为 Guice 在 JDK 11 上的已知反射警告,不影响功能):
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.google.inject.internal.cglib.core.$ReflectUtils$1 (file:/opt/facto/lib/com.google.inject.guice-4.2.3.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of com.google.inject.internal.cglib.core.$ReflectUtils$1
[info] p.a.d.DefaultDBApi - Database [default] initialized
[info] p.a.d.HikariCPConnectionPool - Creating Pool for datasource 'default'
Dropping the database tables (if present) and creating new ones...
Done. Exiting.
命令在输出 Done. Exiting. 后自动退出,无需手动中断。验证建表结果:
mysql -ufacto -pq1w2e3r4 facto -e "SHOW TABLES;"
实测输出:
+---------------------------+
| Tables_in_facto |
+---------------------------+
| BALANCE_CHECKS |
| ENTITY_MODIFICATION_ENTITY|
| EXCHANGE_RATE_MEASUREMENT |
| TRANSACTIONS |
| TRANSACTION_GROUPS |
| USERS |
+---------------------------+
再创建初始管理员:
bin/server -DcreateAdminUser
实测输出:
Creating admin user...
loginName: admin
password: q1w2e3r4
Done. Exiting.
⚠️ 注意:
-DdropAndCreateNewDb会清空并重建全部数据表,仅可用于全新安装;已有真实数据后严禁再执行。
4.8 启动与验证
前台启动(首次验证建议前台跑,便于观察日志):
bin/server
实测输出(节选):
WARNING: All illegal access operations will be denied in a future release
[info] p.a.d.DefaultDBApi - Database [default] initialized
[info] p.a.d.HikariCPConnectionPool - Creating Pool for datasource 'default'
[info] play.api.Play - Application started (Prod) (no global state)
[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
看到 Application started (Prod) 与 Listening for HTTP on ... :9000 即启动成功。另开一个终端逐项验证:
curl -sI http://localhost:9000/
实测输出(未登录访问首页被重定向到登录页):
HTTP/1.1 303 See Other
Location: /login/
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:9000/login/
curl -s http://localhost:9000/externalapi/healthcheck/ -w " [HTTP %{http_code}]\n"
实测输出:
HTTP 200
OK [HTTP 200]
再做一次真实登录验证(登录表单字段名为 loginName/password,可用浏览器打开 http://<服务器IP>:9000/ 用 admin/q1w2e3r4 登录;命令行验证如下):
curl -s -i -X POST 'http://localhost:9000/login/?returnTo=%2Fapp%2F' -d 'loginName=admin&password=q1w2e3r4' | grep -E 'HTTP/|Location' | head -2
实测输出:
HTTP/1.1 303 See Other
Location: /app/
浏览器访问 http://<服务器IP>:9000/app/useradministration 即可进入用户管理页面,添加家庭成员用户。
五、方案 B:发行包离线安装(内网/断网服务器)
5.1 方案说明
适用于目标服务器无法访问 GitHub 的内网环境:在任意能联网的机器上取齐「发行包 + 系统依赖 deb 包」,经内部渠道(如 scp/U 盘/内网软件源)传入后按方案 A 相同步骤安装。核心思路是把「下载」与「安装」两阶段解耦,安装阶段的每条命令与方案 A 完全一致。
5.2 在联网机器上取齐安装物料
下载发行包并校验(与方案 A 同一文件):
wget https://github.com/nymanjens/facto/releases/download/v4.29/release-v4.29.tar.gz
sha256sum release-v4.29.tar.gz
实测输出:
3681d703817ae2a3a80125de270e1bf73436f4474e32a3a7593a80b2e28925a7 release-v4.29.tar.gz
用 apt-get download 把 JDK 与 MariaDB 的 deb 包及其依赖拉到本地目录(也可直接使用内网镜像源安装):
mkdir -p /tmp/debs && cd /tmp/debs
apt-get download openjdk-11-jre-headless
实测输出:
Fetched 42.6 MB in 4s (9543 kB/s)
W: Download is performed unsandboxed as root as file '/tmp/debs/openjdk-11-jre-headless_11.0.32.1+1-1ubuntu1~22.04_amd64.deb' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)
(W 开头一行是 _apt 用户沙箱权限提示,deb 文件已正常落盘,可忽略。)
5.3 传输与安装
将物料传到目标服务器(以 scp 为例,目标服务器地址按实际环境替换):
scp release-v4.29.tar.gz /tmp/debs/*.deb user@192.168.1.100:/opt/install-pkg/
在目标服务器上安装 deb 依赖(dpkg 循环装包时如有依赖顺序问题,重复执行一遍即可收敛):
cd /opt/install-pkg
dpkg -i openjdk-11-jre-headless_11.0.32.1+1-1ubuntu1~22.04_amd64.deb
apt-get install -f -y
随后按方案 A 的 4.3(UTF-8 环境)→ 4.4(MariaDB,内网可用 apt-get install mariadb-server 走内网源)→ 4.5(解压到 /opt/facto)→ 4.6(配置)→ 4.7(初始化)→ 4.8(启动验证)逐节执行,所有命令与实测输出与方案 A 完全一致,此处不再重复。
5.4 方案对比与选择建议
| 维度 | 方案 A 在线发行包 | 方案 B 离线发行包 |
|---|---|---|
| 是否需外网 | 需要(GitHub 下载 84 MB) | 不需要(物料经内网传入) |
| 版本获取 | 直接取 Latest | 提前在联网机取好 |
| 依赖解析 | apt 自动处理 | apt-get download 预先打包或内网源 |
| 步骤数 | 少 | 略多(多了物料传输) |
| 适用场景 | 标准云服务器/有外网出口 | 内网隔离、金融/家庭隐私环境 |
选择建议:能出网选方案 A(最省事);内网隔离选方案 B,物料一次性取齐后可反复安装多台。
六、服务托管:开机自启与进程管理
生产使用必须让 facto 常驻并在开机后自动拉起。官方仓库根目录提供 SysV 脚本 unix-service.sh,同时本文给出等价的 systemd 单元(Ubuntu 22.04 服务器默认使用 systemd)。两种方式二选一即可。
6.1 专用系统账号
两种托管方式都建议使用非 root 的专用账号运行。创建系统账号 facto 并移交目录属主:
useradd -r -d /opt/facto -s /bin/sh facto
chown -R facto:facto /opt/facto
⚠️ 注意:shell 必须是可用的(如 /bin/sh)。若设为 /usr/sbin/nologin,SysV 方式的 su 会拒绝执行并报 This account is currently not available.(实测排障过程见 FAQ-3);systemd 方式不受 shell 限制。
6.2 方式一:SysV init(官方 unix-service.sh)
从官方仓库取脚本(v4.29 根目录 unix-service.sh),复制为 /etc/init.d/facto 并修改三处设置:
wget https://raw.githubusercontent.com/nymanjens/facto/v4.29/unix-service.sh -O /etc/init.d/facto
chmod +x /etc/init.d/facto
需要修改的关键行(原始脚本中的 SERVICE_ROOT=/path/to/facto、USER=root、su pi -c 分别改为实际值):
### settings ##
SERVICE_ROOT=/opt/facto
USER=facto
HOME=/opt/facto
以及 start 分支中的启动命令行,同时注入 JVM 堆限制(见 6.4 节):
export JAVA_OPTS="-Xms256m -Xmx256m"
su $USER -c "cd $SERVICE_ROOT && bin/server" > /tmp/facto-logs 2>&1 &
注册开机自启并测试启停:
update-rc.d facto defaults
实测输出:
update-rc.d: warning: start and stop actions are no longer supported; falling back to defaults
service facto start
实测输出:
Starting facto...
Waiting for RUNNING_PID to appear
Done. Process 6139 was started.
验证服务健康与进程参数:
curl -s http://localhost:9000/externalapi/healthcheck/ -w " [HTTP %{http_code}]\n"
jps -v | grep -v Jps
实测输出:
OK [HTTP 200]
6139 ProdServerStart -Xms256m -Xmx256m -Duser.dir=/opt/facto
停止服务(脚本通过 RUNNING_PID 文件定位进程并优雅 kill -15):
service facto stop
实测输出:
Stopping facto...
Killing process 6139 found in RUNNING_PID
Waiting for RUNNING_PID to disappear...
Done
停止后健康检查立即失效(连接被拒):
curl -s -o /dev/null -w "after-stop: HTTP %{http_code}\n" http://localhost:9000/externalapi/healthcheck/ --max-time 3
实测输出:
after-stop: HTTP 000
6.3 方式二:systemd 单元(Ubuntu 22.04 服务器推荐)
写入 /etc/systemd/system/facto.service(⚠️ 需要宿主具备 systemd 的真实服务器环境;Ubuntu 22.04 服务器默认满足):
[Unit]
Description=Family Accounting Tool (facto)
After=network.target mariadb.service
Wants=mariadb.service
[Service]
Type=simple
User=facto
Group=facto
WorkingDirectory=/opt/facto
Environment="JAVA_OPTS=-Xms256m -Xmx256m"
Environment=LANG=en_US.UTF-8
ExecStart=/opt/facto/bin/server
ExecStop=/bin/kill -15 $MAINPID
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
逐项说明:After/Wants 保证数据库先于应用启动;Environment="JAVA_OPTS=..." 限制 JVM 堆(⚠️ 含空格的值必须整段加英文双引号,写成 Environment=JAVA_OPTS=-Xms256m -Xmx256m 会被 systemd 按空格拆成两个环境变量,导致只有 -Xms256m 生效——实测踩坑);SuccessExitStatus=143 把 JVM 收到 SIGTERM 后的退出码 143 视为正常停止。
加载、开机自启并启动:
systemctl daemon-reload
systemctl enable facto
实测输出:
Created symlink /etc/systemd/system/multi-user.target.wants/facto.service → /etc/systemd/system/facto.service.
systemctl start facto
systemctl status facto --no-pager | head -8
实测输出(节选):
● facto.service - Family Accounting Tool (facto)
Loaded: loaded (/etc/systemd/system/facto.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2026-10-23 03:31:34 UTC; 14s ago
Main PID: 5119 (java)
Tasks: 39 (limit: 94371)
Memory: 266.9M
确认 JVM 堆限制真实生效:
jps -v | grep -v Jps
实测输出:
5249 ProdServerStart -Xms256m -Xmx256m -Duser.dir=/opt/facto
systemctl is-active facto
实测输出:
active
6.4 JVM 堆内存调整说明(256–512MB)
facto 是单 JVM 进程,家庭规模(2–6 名成员、日均几十笔流水)下堆内存 256–512MB 完全够用。调整位置:
- systemd 方式:改
facto.service中Environment="JAVA_OPTS=-Xms256m -Xmx256m"的数值(如-Xms256m -Xmx512m),然后systemctl daemon-reload && systemctl restart facto; - SysV 方式:改
/etc/init.d/facto中export JAVA_OPTS="-Xms256m -Xmx256m"行,然后service facto restart; - 生效验证:
jps -v观察-Xms/-Xmx实际值(见上文实测输出),或ps -ef | grep [j]ava查看完整命令行。
七、升级、降级与多版本管理
官方 README 未提供自动迁移机制,版本升级请遵循「先备份、后替换」原则:
- 备份数据库(唯一权威数据源是 MariaDB):
mysqldump -ufacto -pq1w2e3r4 --databases facto > /root/facto-backup-$(date +%F).sql
(命令执行无回显即成功,ls -la /root/*.sql 可见备份文件。)
- 备份应用目录(保留配置与附件):
cp -a /opt/facto/conf /root/facto-conf-backup
cp -a /opt/facto/attachments /root/facto-attachments-backup
- 下载新版本发行包并解压到新目录(如
/opt/facto-v4.30),把旧conf/application.conf的四处改动(secret key、数据库 URL、附件目录、默认密码)合并进新包的配置;conf/accounting-config.yml若做过家庭化定制也一并迁移; - 切换运行目录:停止服务 → 将服务脚本/单元中的
SERVICE_ROOT或WorkingDirectory指向新目录 → 启动; - 验证:
curl健康检查与登录流程(见 4.8 节)。
降级与多版本:由于每个版本都是自包含目录(/opt/facto-v4.28、/opt/facto-v4.29…),多版本并存只需要把服务指向不同目录;数据回退则用第 1 步的 mysqldump 恢复(mysql -ufacto -pq1w2e3r4 facto < 备份文件.sql)。⚠️ 切勿对新数据执行 -DdropAndCreateNewDb,该参数会清空全部表(4.7 节实测日志可证)。
八、常见问题(FAQ)
Q1:启动报 MalformedInputException: Input length = 1,应用无法初始化
错误现象(实测日志关键行):
[error] a.m.a.c.ConfigModule - Error when parsing accounting-config.yml: java.nio.charset.MalformedInputException: Input length = 1
at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:274)
定位:accounting-config.yml 内含非 ASCII 字符(如货币符号 £),应用按 JVM 默认字符集读取;locale 输出显示系统为 POSIX 环境(LANG= 为空),JVM 默认字符集退化为 ASCII,读到非 ASCII 字节即抛异常。
解决:按 4.3 节生成 en_US.UTF-8 语言环境,并在启动命令的会话中 export LANG=en_US.UTF-8(或写入 systemd 单元 Environment=LANG=en_US.UTF-8),重新执行初始化命令后实测输出:
Dropping the database tables (if present) and creating new ones...
Done. Exiting.
Q2:登录接口返回 HTTP 400
现象:curl -X POST -d 'username=admin&password=...' http://localhost:9000/login/ 返回 400。
定位:登录表单字段名不是 username。实测抓取登录页表单可见真实字段名:
curl -s http://localhost:9000/login/ | grep -oE '<input[^>]*>'
实测输出:
<input class="form-control" placeholder="Username" name="loginName" value="" autofocus />
<input class="form-control" placeholder="Password" name="password" type="password" value="" />
解决:使用 loginName=admin&password=q1w2e3r4 提交即返回 303(见 4.8 节实测)。另外密码错误同样返回 400,排查时先分清「字段名错」还是「密码错」。
Q3:service facto start 启动超时,/tmp/facto-logs 显示账户不可用
错误现象(实测):
Waiting for RUNNING_PID to appear
Timed out
cat /tmp/facto-logs 输出:
This account is currently not available.
定位:服务账号 shell 被设为 /usr/sbin/nologin,官方脚本用 su $USER -c 切换用户执行命令时被 PAM 拒绝。
解决:给服务账号一个可用的最小 shell:
usermod -s /bin/sh facto
再次 service facto start 实测输出:
Done. Process 5987 was started.
Q4:启动日志出现 WARNING: An illegal reflective access operation has occurred
这是 Google Guice 4.2.3 在 JDK 11 上的已知反射警告(Guava/Guice 生态普遍存在),三条 WARNING 之后应用照常输出 Application started (Prod),不影响功能,无需处理;升级到 JDK 17 世代该警告会变成拒绝执行,因此保持 JDK 11。
Q5:忘记 admin 密码怎么办
重新执行一次管理员创建命令即可(按 4.6 节先把 app.setup.defaultPassword 改成想要的值):
bin/server -DcreateAdminUser
实测输出会重新打印生成的账号密码(loginName: admin / password: q1w2e3r4)。⚠️ 执行前停止服务、执行后重新启动;该命令只重建 admin 账号,不动流水数据。
九、卸载与清理
按依赖的反向顺序彻底移除(数据库文件含全部记账数据,卸载前确认不再需要):
systemctl disable --now facto 2>/dev/null || service facto stop # 停止服务(二选一按实际托管方式)
rm -f /etc/systemd/system/facto.service /etc/init.d/facto # 移除服务定义
systemctl daemon-reload 2>/dev/null
rm -rf /opt/facto # 移除应用本体与附件
mysql -e "DROP DATABASE IF EXISTS facto;" # 删除数据库
mysql -e "DROP USER IF EXISTS 'facto'@'localhost';" # 删除数据库账号
userdel facto 2>/dev/null # 删除系统账号
apt-get purge -y mariadb-server openjdk-11-jdk-headless # 移除系统依赖(可选)
apt-get autoremove -y
(disable --now 的回显含 Removed "/etc/systemd/system/multi-user.target.wants/facto.service".;其余命令成功均无回显。)最后确认端口与进程已消失:
ss -ltn | grep 9000 ; jps -v | grep -v Jps
两条均无输出即为干净状态。
十、参考链接
- 官方仓库与 README(安装章节):
https://github.com/nymanjens/facto - Releases 页(发行包资产
release-v4.29.tar.gz):https://github.com/nymanjens/facto/releases - v4.29 unix-service.sh(SysV 服务脚本):
https://github.com/nymanjens/facto/blob/v4.29/unix-service.sh - Docker 形态限制讨论(issue #4):
https://github.com/nymanjens/facto/issues/4 - Play Framework 2.8 文档(应用配置机制):
https://www.playframework.com/documentation/2.8.x - Ubuntu OpenJDK 11 包页:
https://packages.ubuntu.com/jammy/openjdk-11-jdk-headless