项目简介
Securo 是一款自托管的个人财务管理应用,让用户完全掌控自己的财务数据。支持多账户管理、交易记录、预算、储蓄目标、资产跟踪,并可通过 Pluggy、Enable Banking 和 SimpleFIN 等提供商进行银行同步。该应用以隐私为核心,确保第三方无法访问您的数据。
README 预览
\n \n\nSecuro\n\n \n \n \n \n \n \n \n \n Website · Demo · Roadmap · Docs · Discord · Talk to the maintainer\n\n\nFinance apps want your data. This one doesn't.\n\n\nWe believe personal finance should actually be personal. No corporation should sit between you and your financial data. Securo is an open-source finance manager that runs on your own infrastructure, giving you full visibility into your accounts, spending, and habits, without surrendering a single byte to third parties. Take back control.\n\n\n## Quick Start\n\n**Linux & macOS** (uses Docker or Podman; installs Docker if neither is present):\n\n```bash\ncurl -fsSL https://usesecuro.com/install.sh | bash\n```\n\n**Windows:** Install [Docker Desktop](https://www.docker.com/products/docker-desktop/), then:\n\n```bash\ngit clone https://github.com/securo-finance/securo.git && cd securo\ndocker compose up --build\n```\n\nOpen [http://localhost:3000](http://localhost:3000) and create an account. That's it.\n\n\n \n\n\n## Features\n\n- Multi-account management with running balances\n- Transaction management with search, filters, and CSV export\n- File import (OFX, QIF, CAMT, CSV)\n- Auto-categorization rules engine\n- Recurring transactions and budgets\n- Goals and savings targets with progress tracking\n- Asset management with valuation tracking and growth rules\n- Reports: Net Worth and Income vs Expenses with category sparklines\n- Bank sync via providers (Pluggy for Brazilian banks, Enable Banking for ~2500 European PSD2 banks, SimpleFIN for US and international banks, extensible)\n- Multi-currency support with automatic FX conversion\n- Multi-user support with admin panel and registration controls\n- Two-factor authentication (TOTP) with brute-force protection\n- OIDC login support for Authentik, Pocket ID, and other standard providers\n- AI Agents (optional): self-hosted LLM chat with tool-use over your data, plus a per-agent RAG knowledge base\n\n## Bank Sync (Optional)\n\nAdd credentials for
常见问题 (5)
故障排除为什么 SQLAlchemy 在从 SQLite 读取 UUID 时会抛出 "'float' object has no attribute 'replace'" 错误?
SQLite 会将声明为 UUID 的列赋予 NUMERIC 亲和性(affinity),因此某些 UUID 十六进制字符串(例如 1e999...)会被静默转换为 REAL/浮点数。随后,SQLAlchemy 的 UUID 结果处理器接收到的是浮点数而不是字符串,并对其调用 .replace()。
要修复 SQLite 测试 schema,可在编译时将 PostgreSQL UUID 编译为 CHAR(32),该类型具有 TEXT 亲和性:
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.ext.compiler import compiles
@compiles(PG_UUID, "sqlite")
def _uuid_as_char32(element, compiler, **kw):
return "CHAR(32)"
在 Base.metadata.create_all() 或引擎/表创建之前注册此规则。请重新创建任何已有的基于文件的 SQLite 测试数据库;内存数据库会自动重新创建。PostgreSQL 不受影响,将继续使用其原生的 uuid 类型。
故障排除为什么在使用 podman-compose 时 FRONTEND_URL 会被追加一个 '}'?
这是由低于 1.6.0 版本的 podman-compose 引起的,该版本无法解析 compose 文件中类似 ${FRONTEND_URL:-http://localhost:${FRONTEND_PORT:-3000}} 的嵌套默认值。解析器在第一个 } 处停止,导致末尾的大括号残留在值中,因此 printenv FRONTEND_URL 会显示 https://example.mydomain.com}。修复方法:将 podman-compose 升级到 1.6.0 或更高版本(例如 pip install -U podman-compose),然后使用 podman-compose up -d 重新创建容器。或者,更新到本项目已移除嵌套变量语法的版本,这样就可以兼容任何版本的 podman-compose。
故障排除为什么在特定日期之后,测试会失败并报错"Due date cannot precede the issue date"?
测试套件使用了硬编码的冻结日期(例如 TODAY = date(2026, 8, 26)),而服务通过 datetime.now(timezone.utc).date() 基于真实的 UTC 时钟生成 issue_date。一旦真实时间超过冻结的 due_date(TODAY + 10 天),校验 due_date < issue_date 将拒绝该发票,所有依赖它的测试都会永久性失败。修复方法:将测试锚定到与服务相同的时钟——把 TODAY = date(2026, 8, 26) 替换为 TODAY = datetime.now(timezone.utc).date()。该问题已由上游的 PR #852 修复,该 PR 将发票测试套件固定在其自身的时钟上;升级到包含 #852 的版本同样可以解决问题。不要只是把字面量日期往后推(例如 date(2027, 1, 1))——这只会在此后重现同样的失败。注意:测试常量在导入时求值,而服务在调用时才读取时钟,因此请在 issue_date 与 due_date 之间保留多天的余量,以避免跨越 UTC 日期边界时出现不稳定的失败。
故障排除当跨工作区组的成员在 Securo 中保存拆分时,如何修复"One or more split members not found"错误?
此回归问题影响 Securo v0.16.0。PR #375 在 split_service._validate_members 中添加了严格的 Group.workspace_id == workspace_id 检查,导致链接到来自其他工作区的共享组的用户被拒绝,尽管 UI 中显示了该组。
修复方法是升级到包含 PR #972 的版本。如果需要自行修补 v0.16.0,请更新 split_service._validate_members,使其使用与 group_service._visible_predicate 相同的可见性规则:允许属于当前工作区的组,或者用户以非所有者成员身份链接的组:
member_groups = (
select(GroupMember.group_id)
.where(
GroupMember.linked_user_id == user_id,
GroupMember.is_self.is_(False),
)
.distinct()
)
result = await session.execute(
select(GroupMember, Group)
.join(Group, GroupMember.group_id == Group.id)
.where(
GroupMember.id.in_(member_ids),
or_(
Group.workspace_id == workspace_id,
Group.id.in_(member_groups),
),
)
)
建议复用/导入 group_service._visible_predicate,而不是重复实现该规则,这样读写路径就不会再次出现分歧。应用补丁后,请重新构建/重启自托管容器。这仍然可以阻止引用来自其他工作区的任意组;只有请求用户明确链接到的组才会被接受。
故障排除为什么 Enable Banking 的交易显示"Transaction"而不是债权人或债务人的名称?
当 remittance_information 和 additional_information 为空时会出现这种情况;Securo 会直接回退到 "Transaction",而不是使用交易对手名称。修复 Enable Banking 的导入映射,使其在检查 remittance 和 additional information 之后,对借记(DBIT)交易使用 creditor.name,对贷记(CRDT)交易使用 debtor.name,然后使用相反的交易对手,最后才使用 "Transaction":
description = _join_remittance(raw.get("remittance_information")) or (
raw.get("additional_information") or ""
)
if not description:
creditor = (raw.get("creditor") or {}).get("name") or raw.get("creditor_name")
debtor = (raw.get("debtor") or {}).get("name") or raw.get("debtor_name")
if indicator == "DBIT":
description = creditor or debtor or ""
else:
description = debtor or creditor or ""
description = description.strip()[:500] or "Transaction"
应用 Enable Banking 交易对手回退补丁,或升级到包含 PR fix(enable-banking): fall back to counterparty name when description is missing 的版本。修复后,现有交易可能需要重新导入或手动更正。