Ubuntu 22.04 安装 Family Accounting Tool v4.29 指南

在 Ubuntu 22.04 上从零安装 Family Accounting Tool v4.29 的完整指南:依赖链、官方发行包在线/离线方案、MariaDB 准备、初始化、systemd/SysV 托管、JVM 堆限制与排障实录。

Family Accounting Tool财务记账自托管2026/10/23

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.292026-08-01有(release-v4.29.tar.gz,GitHub 标记 Latest)本文安装目标:最新可安装稳定版
v4.302026-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 未提供自动迁移机制,版本升级请遵循「先备份、后替换」原则:

  1. 备份数据库(唯一权威数据源是 MariaDB):
mysqldump -ufacto -pq1w2e3r4 --databases facto > /root/facto-backup-$(date +%F).sql

(命令执行无回显即成功,ls -la /root/*.sql 可见备份文件。)

  1. 备份应用目录(保留配置与附件):
cp -a /opt/facto/conf /root/facto-conf-backup
cp -a /opt/facto/attachments /root/facto-attachments-backup
  1. 下载新版本发行包并解压到新目录(如 /opt/facto-v4.30),把旧 conf/application.conf 的四处改动(secret key、数据库 URL、附件目录、默认密码)合并进新包的配置;conf/accounting-config.yml 若做过家庭化定制也一并迁移;
  2. 切换运行目录:停止服务 → 将服务脚本/单元中的 SERVICE_ROOT 或 WorkingDirectory 指向新目录 → 启动;
  3. 验证: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