# Release、安装与更新 TallyNote 的发布包必须在目标 Linux 架构上构建。`better-sqlite3`、`argon2`、`sharp` 和 Node runtime 都包含原生代码,不能在 macOS 上交叉打包后冒充 Linux。 正式支持:Linux x86_64/amd64;脚本和安装器也支持在原生 runner 上提供 Linux aarch64/arm64(glibc 或 musl)。当前仓库 workflow 只生成 x64,arm64 必须使用对应 runner 单独构建发布。ARMv7/ARM32 只在你拥有对应 runner 和完整依赖构建结果时实验使用。Linux x86 32 位(i386、i686、ia32)明确不支持,Node.js 24 及原生依赖没有可维护的正式构建,因此安装器会拒绝它。 ## 自动发布 向 Gitea 推送符合 SemVer 的 tag(例如 `v1.1.2`)会触发 `.gitea/workflows/release.yml`: 1. 在 Linux runner 上安装依赖,执行 `pnpm check`、`pnpm test` 和 `pnpm release:build`。 2. 由 `scripts/publish-gitea-release.sh` 计算所有归档的 `SHA256SUMS`。 3. 如果提供 Ed25519 私钥则生成 `SHA256SUMS.sig`,通过 Gitea Releases API 创建/复用对应 Release,并幂等上传归档、清单和可选签名。 发布脚本会根据当前 tag 与上一个版本 tag 之间的真实 Git 提交自动生成 Release 正文,按“新增功能、问题修复、优化与重构、文档与测试”分类,并以 Markdown 写入 Gitea。Gitea 页面会渲染这些标题和列表;更新中心读取同一份正文后再进行安全的 Markdown 子集渲染,不会显示 Markdown 源代码。旧版本曾使用单行占位正文 `TallyNote <版本>`,新版本发布时不会再使用该占位内容。 在仓库的 Actions secrets 配置: - `GITEA_TOKEN`:仅授予当前仓库 Release 写权限的 token。 - `TALLYNOTE_RELEASE_SIGNING_KEY`:可选的 Ed25519 私钥 PEM。它只作为 CI secret 使用,绝不能提交到 Git。 也可以在 Linux 发布机上手动执行: ```bash pnpm install --frozen-lockfile pnpm check && pnpm test pnpm release:build 1.1.2 ./release GITHUB_REPOSITORY=awaioi/TallyNote \ GITEA_TOKEN=... \ ./scripts/publish-gitea-release.sh v1.1.2 ./release ``` 发布资产名称必须包含当前平台,例如 `tallynote-1.1.2-linux-x64-glibc.tar.gz`。构建脚本会同时生成完整安装包和轻量更新包:`tallynote-1.1.2-linux-x64-glibc.tar.gz` 用于首次安装,`tallynote-1.1.2-linux-x64-glibc.update-<锁文件 SHA256>.tar.gz` 仅用于复用现有运行时的后台更新。同一个 Release 只保留一个 `SHA256SUMS`;有签名时再保留一个 `SHA256SUMS.sig`,签名覆盖清单完整原文。 ## curl 安装 安装器默认直接获取并安装 latest Release。它始终校验 `SHA256SUMS` 中的 SHA-256,不要求公钥或签名文件: ```bash curl --proto '=https' --tlsv1.2 -fsSL https://git.awaioi.com/awaioi/TallyNote/raw/branch/main/install.sh | sudo bash ``` 首次在交互式 SSH/终端中执行时,安装器会在下载前询问监听方式和端口(端口可直接回车使用默认值),并检查所选 TCP 端口是否已被占用。可选择仅本机监听 `127.0.0.1`,或监听 `0.0.0.0` 以允许通过真实服务器 IP/域名访问;选择公网监听时会尝试通过 HTTPS 自动获取公网 IPv4,将 `http://公网IP:端口` 作为默认访问地址,也可以手动改填域名。公网 HTTP 必须在提示中明确确认,公开地址不能填写通配监听地址。服务启动后,安装器会先请求本机 `/health`,只有健康检查通过才会报告安装完成并输出最终访问链接。监听 `127.0.0.1` 时链接只对服务器本机有效;需要公网或其他设备访问时请选择 `0.0.0.0`。健康检查失败时会输出 systemd 状态和最近日志并回滚本次切换。已有安装升级时不会重复询问,并保留现有环境文件。无终端或 CI 使用 `--non-interactive`(默认 `127.0.0.1:3000`),也可通过 `TALLYNOTE_HOST`、`TALLYNOTE_PORT`、`TALLYNOTE_PUBLIC_ORIGIN` 和 `TALLYNOTE_ALLOW_INSECURE_HTTP` 显式配置。 非交互安装命令: ```bash curl --proto '=https' --tlsv1.2 -fsSL https://git.awaioi.com/awaioi/TallyNote/raw/branch/main/install.sh | sudo bash -s -- --non-interactive ``` 脚本会从 `https://git.awaioi.com/awaioi/TallyNote/releases/download/v<版本>/` 下载当前架构归档和 `SHA256SUMS`,限制 HTTPS 重定向只能落在配置的受信主机,校验压缩/展开大小、条目数量、路径和特殊文件,再原子切换 `/opt/tallynote/current`。自定义仓库时同时设置 `TALLYNOTE_REPOSITORY_URL`、`TALLYNOTE_RELEASE_API_URL` 和 `TALLYNOTE_RELEASE_ALLOWED_HOSTS`;若使用独立 CDN,必须把 CDN 主机显式加入白名单。需要预览时显式加 `--dry-run`,需要固定版本时使用 `--version`。 安装器会在每个关键阶段输出统一格式的日志,便于在 SSH 或 systemd 安装会话中确认进度;交互式终端下载时还会显示 curl 进度条,CI 或日志重定向时则保持纯文本输出: ```text tallynote installer: [阶段] 检查运行环境、权限和目标架构 tallynote installer: [阶段] 从 Release API 获取最新版本 tallynote installer: [阶段] 获取发布包:tallynote-<版本>-linux-x64-glibc.tar.gz tallynote installer: [阶段] 获取 SHA-256 校验清单 tallynote installer: [阶段] 校验 SHA-256 和发布签名 tallynote installer: [阶段] 解包、校验包结构并原子切换到版本 <版本> tallynote installer: [完成] 版本 <版本> 已切换为当前版本 tallynote installer: [阶段] 安装 systemd 单元、更新辅助程序和卸载器 tallynote installer: [完成] systemd 单元、更新辅助程序和卸载器已安装 tallynote installer: [阶段] 重新加载 systemd 并启动 TallyNote tallynote installer: [完成] TallyNote 服务已启用并启动 tallynote installer: [阶段] 清理旧版本并完成安装 tallynote installer: [完成] 旧版本清理完成 tallynote installer: [完成] 安装完成:TallyNote <版本> tallynote installer: 访问地址:http://127.0.0.1:<端口> ``` 每个阶段完成时会输出 `[完成]`;错误会立即以 `tallynote installer:` 前缀输出,不会静默等待或切换半成品版本。 如需启用签名校验,设置 `TALLYNOTE_INSTALL_REQUIRE_SIGNATURE=true` 并提供 `--signing-key`;后台更新同样可通过 `TALLYNOTE_UPDATE_REQUIRE_SIGNATURE=true` 和 `TALLYNOTE_UPDATE_PUBLIC_KEY_FILE` 开启。默认关闭签名要求,方便公开自维护仓库直接更新。 已有安装默认拒绝安装不高于当前版本的 release;只有在明确执行 `--allow-downgrade`(或设置 `TALLYNOTE_ALLOW_DOWNGRADE=true`)时才允许回退版本。 安装器拒绝预先存在的符号链接、非 root 拥有或对组/其他用户可写的安装、配置和备份目录。发布包同时携带 `uninstall.sh`,安装后会落到 `/usr/local/sbin/tallynote-uninstall`,并提供 `/usr/local/sbin/tallynote-admin-init` 作为生产环境首次管理员初始化入口。`--allow-unsigned` 作为旧版本兼容参数保留。 安装布局: ```text /opt/tallynote/releases// # 只读发布代码 /opt/tallynote/current -> releases/ /opt/tallynote/.update-work/ # 0700 root:root,root 更新器临时工作区 /opt/tallynote/.update-state # root 更新状态标记,异常中断后用于恢复 /var/lib/tallynote/ # SQLite、附件、暂存和导出 /var/lib/tallynote-backups/ # 更新前数据备份 /etc/tallynote/tallynote.env ``` ## 卸载与数据保留 默认卸载只移除发布代码、systemd 单元、更新辅助程序和已知配置,数据目录与更新备份不会删除: ```bash sudo /usr/local/sbin/tallynote-uninstall ``` 只有显式 `--purge-data --yes` 才会删除 SQLite、附件、暂存、导出、更新队列和备份;`--purge-config` 可在确认配置目录中没有其他文件后移除空配置目录。卸载器不会自动删除 `tallynote` 系统用户,也不会跟随符号链接删除目录。检测到 `.update-state` 或 `update-request.json` 时会拒绝执行,确认更新已经停止后使用 `--force`。 卸载过程中会输出每个 systemd 单元的检查、停止、禁用和删除阶段;systemd/dbus 调用默认 30 秒超时,避免长时间无反馈。可用 `TALLYNOTE_UNINSTALL_SYSTEMCTL_TIMEOUT_SECONDS` 调整。 ## 后台一键更新 将环境文件中的 `TALLYNOTE_UPDATE_STRATEGY=systemd`、`TALLYNOTE_UPDATE_METADATA_URL` 和 `TALLYNOTE_UPDATE_ALLOWED_HOSTS` 配好后,后台“系统更新”会读取 Gitea 的 `/api/v1/repos///releases/latest`。检查结果只显示当前平台匹配且通过 SHA-256 校验的资产;如果配置了 `TALLYNOTE_UPDATE_PUBLIC_KEY_FILE` 并启用签名要求,再额外验证 Ed25519 签名。 后台更新分为两个明确阶段。管理员先在“系统更新”读取最新 Release 的版本号、发布时间和更新说明,点击“下载更新包”;当前安装如果存在匹配的锁文件指纹,更新器会自动选择轻量 `update-<锁文件 SHA256>` 资产,仅下载 `dist`、迁移和版本元数据,并复用当前版本的 Node 与生产依赖;如果运行时指纹不匹配或轻量包不可用,则自动选择完整安装包。root 更新器会在主服务继续运行时下载、校验 SHA-256、解包并暂存。页面显示“下载完成,等待应用”后,管理员再点击“立即更新”。应用阶段才会短暂停止服务、备份数据、切换 release、启动并执行健康检查;页面显示重启倒计时并自动重试连接。浏览器只提交版本号、任务 ID 和确认标志,不能提交 URL 或文件路径。 Web 进程把受保护的任务文件交给 root 的 `tallynote-update.path`/`tallynote-update.service`,root runner 会重新读取配置源并验证 metadata、清单和暂存目录,不信任队列文件中的 URL 或摘要。切换失败或健康检查失败会恢复旧版本;手动回滚: ```bash sudo /usr/local/sbin/tallynote-update --rollback ``` 更新检查、下载和应用接口分别带有冷却时间(可用 `TALLYNOTE_UPDATE_CHECK_COOLDOWN_SECONDS`、`TALLYNOTE_UPDATE_DOWNLOAD_COOLDOWN_SECONDS`、`TALLYNOTE_UPDATE_APPLY_COOLDOWN_SECONDS` 调整),避免反复触发外部请求或重复排队。服务单元默认仅监听 `127.0.0.1`;首次安装时可在交互提示中选择 `0.0.0.0` 和真实的服务器 IP/域名。直连 HTTP 会暴露未加密的会话和数据,只适合受控网络;绑定域名后必须改为 HTTPS 反向代理,设置真实 `TALLYNOTE_PUBLIC_ORIGIN`、`TALLYNOTE_COOKIE_SECURE=true`、`TALLYNOTE_ALLOW_INSECURE_HTTP=false` 和明确的 `TALLYNOTE_TRUST_PROXY` 跳数。自动化安装可使用 `--non-interactive` 或显式网络环境变量。 更新任务详情按发起管理员隔离,任务错误只返回固定提示,不会把服务器路径、命令输出或上游响应泄露到浏览器;同一时刻仍只允许一个系统更新任务。 业务导出不是备份。停服后复制完整 `/var/lib/tallynote` 数据目录(含数据库、WAL/SHM、附件、暂存、导出和更新任务文件),并限制 SSH、备份和磁盘权限。拥有服务器文件权限的人仍可直接读取底层财务数据。