OpenSource-Hub

securo

アプリケーション

securo-finance/securo

オープンソースのセルフホスト型個人財務管理ツール。プライバシーを重視。

概要

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 

FAQ (5)

トラブル対応
SQLAlchemyがSQLiteからUUIDを読み取る際に「'float' object has no attribute 'replace'」というエラーをraiseするのはなぜですか?

SQLiteはUUIDとして宣言されたカラムにNUMERICアフィニティを割り当てるため、1e999...のような一部のUUIDのhex文字列が暗黙のうちにREAL/floatに変換されてしまいます。その結果、SQLAlchemyのUUID結果プロセッサは文字列ではなくfloatを受け取り、それに対して.replace()を呼び出します。

PostgreSQLのUUIDをTEXTアフィニティを持つCHAR(32)としてコンパイルすることで、SQLiteのテストスキーマを修正します:

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型を使用します。

参照 Issue #820
トラブル対応
podman-compose を使用すると、なぜ FRONTEND_URL に '}' が追加されてしまうのですか?

これは、1.6.0 より古い podman-compose のバージョンが原因で発生します。これらのバージョンでは、${FRONTEND_URL:-http://localhost:${FRONTEND_PORT:-3000}} のような compose ファイル内のネストされたデフォルト値を解析できません。パーサーは最初の } で処理を停止し、末尾のブレースが値に付いたままになるため、printenv FRONTEND_URL は https://example.mydomain.com} と表示されます。修正方法: podman-compose を 1.6.0 以降にアップグレードし(例: pip install -U podman-compose)、その後 podman-compose up -d でコンテナを再作成してください。あるいは、ネストされた変数構文を削除したこのプロジェクトのバージョンに更新すれば、どの podman-compose バージョンでも動作するようになります。

参照 Issue #1162
トラブル対応
なぜ特定の日付以降、テストが '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 によって修正されており、請求書テストスイートが独自のクロックに固定されるようになっています。#852 を含むバージョンに更新することでも解決できます。リテラルを単に先へ進めるだけ(例: date(2027, 1, 1))の対応は避けてください — 後で同じ失敗が再発します。注意: テストの定数はインポート時に評価されるのに対し、サービスは呼び出し時にクロックを読み取るため、UTC の日付境界をまたいだフレーキーな失敗を避けるために、発行日と期日の間に数日分の余裕を確保してください。

参照 Issue #851
トラブル対応
クロスワークスペースグループのメンバーが Securo で split を保存する際に「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 を再利用/インポートすることが望ましく、これにより読み取りパスと書き込みパスが再び乖離するのを防げます。パッチ適用後は、セルフホストのコンテナを再ビルド/再起動してください。これにより、他のワークスペースの任意のグループが参照されることは依然として防がれ、リクエスト元のユーザーが明示的にリンクされているグループのみが受け入れられます。

参照 Issue #972
トラブル対応
なぜ Enable Banking の取引で、債権者または債務者の名前の代わりに「Transaction」と表示されるのですか?

これは remittance_information と additional_information が空の場合に発生します。Securo は取引相手名を使用する代わりに、直接 "Transaction" にフォールバックします。Enable Banking のインポートマッピングを修正し、remittance 情報と additional 情報をチェックした後、デビット(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 を含むリリースにアップグレードしてください。修正後は、既存の取引を再インポートするか手動で修正する必要がある場合があります。

参照 Issue #734