TallyNote
TallyNote 是一个本地优先的采购报销记录网站:记录支付时间、金额、备注、付款凭证和发票,按月整理后导出 Excel 与原始附件。
每笔活动账目至少需要一张付款凭证;发票与“无发票原因”严格二选一。没有发票时,在新增或编辑抽屉勾选“无发票”并填写原因,原因会显示在列表、详情和导出的 Excel“无发票原因”列中。删除最后一张发票时,系统也会在确认弹窗中要求填写原因,并与删除操作原子保存。
导出 ZIP 默认包含 报销清单.xlsx 和附件目录。账目列表中的“包含 manifest.json”选项默认关闭;开启后会额外导出附件元数据及 SHA-256 校验值清单。
导出目录示例:
TallyNote_报销资料_xxxxxxxx.zip
├── 报销清单.xlsx
├── 001_20260827_12.34_ab12cd34/
│ ├── 付款凭证/
│ │ └── 付款截图.png
│ └── 发票/
│ └── invoice.pdf
└── manifest.json # 仅勾选“包含 manifest.json”时生成
本地运行
pnpm install
pnpm admin:init
pnpm dev
生产模式:
pnpm build
pnpm start
默认开发和生产构建都使用腾讯 TDesign React 前端。需要单独检查或构建前端时,可以使用:
pnpm check:next
pnpm build:next
build:next 与 pnpm build 一样输出到 dist/web,可直接由生产 Fastify 服务提供。
首次初始化会要求交互式输入管理员密码。也可以使用 pnpm admin:init -- --username admin --display-name 管理员 --generate 生成一次性临时密码。
默认地址为 http://127.0.0.1:3000,开发界面为 http://127.0.0.1:5173。配置项见 .env.example。
无 Docker 安装(systemd)
安装器正式支持 Linux x86_64(x64),脚本和运行时也支持在对应原生 runner 上发布 aarch64(arm64);当前仓库内置 workflow 只生成 x64,arm64 需要在原生 ARM64 runner 上单独构建并发布。ARMv7/ARM32 仅实验性支持;Linux x86 32 位(i386、i686、ia32)明确不支持,因为 Node.js 24 和项目原生依赖没有可维护的官方构建。不要在 32 位系统上强行安装。
发布包必须包含 dist/、生产依赖、匹配架构的 Node runtime、systemd 单元,以及 SHA256SUMS 和 SHA256SUMS.sig。安装器默认 dry-run,只有显式 --apply 才会下载或写盘;正式安装必须提供独立核对过的 Ed25519 公钥:
curl --proto '=https' --tlsv1.2 -fsSL \
https://git.awaioi.com/awaioi/TallyNote/raw/branch/main/install.sh \
| sudo bash -s -- --apply --version 1.1.0 \
--signing-key /root/tallynote-update.pub \
--update-public-key-file /root/tallynote-update.pub
指定版本时,脚本会从 https://git.awaioi.com/awaioi/TallyNote/releases/download/v<版本>/ 获取归档、SHA256SUMS 和签名。也可以通过 TALLYNOTE_REPOSITORY_URL、TALLYNOTE_RELEASE_API_URL、TALLYNOTE_RELEASE_ALLOWED_HOSTS 和 --release-base-url 指向自己的仓库或受信 CDN。--allow-unsigned 仅供隔离开发机测试,不能用于公网或真实财务数据。
已有安装默认拒绝降级到不高于当前版本;确需回退时显式使用 --allow-downgrade,正常更新不会覆盖当前或更高版本。
安装布局为 /opt/tallynote/releases/<version> 加 /opt/tallynote/current 符号链接;切换通过临时链接和原子重命名完成。root 更新器使用前缀下独立的 /opt/tallynote/.update-work(0700 root:root)和 .update-state 恢复标记,不会把 root 解包工作区放进应用可写暂存目录。SQLite 数据、附件、暂存、导出和更新队列始终在外置 /var/lib/tallynote,不会随版本包删除。服务单元位于 /etc/systemd/system/tallynote.service,配置文件为 /etc/tallynote/tallynote.env,默认仅监听 127.0.0.1:3000。
升级有两种方式:
- 后台进入“系统更新”,点击“检查更新”后确认版本。应用只会把经过 HTTPS、主机白名单、SHA-256 和 Ed25519 签名校验的请求写入队列;root 权限的
tallynote-update.path/tallynote-update.service会重新获取配置源、验证签名,再执行停机、备份、切换和健康检查。Web 进程没有systemctl权限,队列中的 URL、文件地址和摘要不会直接驱动 root 下载。 - 手动执行
sudo /usr/local/sbin/tallynote-update --rollback可切回上一份 release。更新失败会自动保留旧版本并尝试恢复;不要删除/var/lib/tallynote。
更新任务详情按发起管理员隔离;失败信息在浏览器中使用固定提示,不暴露服务器路径、命令输出或上游响应。系统同一时刻只允许一个更新任务。
公网反代必须使用 HTTPS,并在环境文件中设置真实的 TALLYNOTE_PUBLIC_ORIGIN=https://...、TALLYNOTE_COOKIE_SECURE=true 和明确的 TALLYNOTE_TRUST_PROXY 跳数(不要使用生产值 true)。
构建发布包
在目标 Linux 架构的 CI runner 上执行(不能在 macOS 上冒充 Linux 架构):
pnpm install --frozen-lockfile
pnpm release:build 1.1.0 ./release
将生成的 tallynote-<版本>-linux-<架构>-<libc>.tar.gz 上传到同一个 Gitea Release。推荐由 .gitea/workflows/release.yml 自动执行 scripts/publish-gitea-release.sh,统一生成并上传 SHA256SUMS 与 SHA256SUMS.sig;当前仓库还没有首个 tag/release 时,后台会明确显示不可用,不会下载未验证文件。CI 需要 GITEA_TOKEN 和 TALLYNOTE_RELEASE_SIGNING_KEY secrets。
版本由 package.json 和 Git tag 双重约束:两者必须相同(例如 1.1.0 与 v1.1.0),workflow 会在构建前拒绝不一致的 tag。发布一个版本:
git add .
git commit -m "release: 1.1.0"
git tag -a v1.1.0 -m "TallyNote 1.1.0"
git push origin main --follow-tags
Docker
docker compose up -d --build
docker compose run --rm --no-deps tallynote node dist/server/cli/admin-init.js --username admin --display-name 管理员 --generate
只运行一个应用副本,并将 /data 作为持久化卷。SQLite、附件和导出文件必须位于同一台主机的本地文件系统;不支持 NFS/NAS 或多个副本共享 SQLite。
备份
业务导出不是系统备份。停服后复制完整数据目录(数据库、WAL/SHM、files/、staging/、exports/ 和更新任务文件),恢复时保持目录 0700、文件 0600 权限,并在启动前确保没有其他 TallyNote 进程使用该目录。更新器会在切换前额外写入 /var/lib/tallynote-backups/,但仍建议保留服务器级备份。
应用层会拒绝非 HTTPS 更新源、未匹配主机、无 SHA-256/签名的归档、路径穿越、特殊文件和符号链接;附件与导出下载需要登录并写入审计。拥有服务器文件权限的人仍然可以直接读取 SQLite 和附件,部署时应限制 SSH、备份和磁盘权限,并通过 HTTPS 反代访问。