feat(db): migrate credentials to v2.1 (add secret_ciphertext column)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 10:51:28 +08:00
parent ee851f719b
commit 127e9b6019

View File

@@ -7,7 +7,7 @@ sqlite_master, visible in DBeaver / Navicat.
Schema version management: Schema version management:
- PRAGMA user_version = 2 (set in connection.py after ensure_schema). - PRAGMA user_version = 2 (set in connection.py after ensure_schema).
- _schema_meta row schema_version=2 (written by migrate_v1_to_v2). - _schema_meta schema_version: "2" after migrate_v1_to_v2, "2.1" after migrate_v2_to_v2_1.
""" """
import sqlite3 import sqlite3
@@ -93,9 +93,10 @@ CREDENTIALS_TABLE_SQL = textwrap.dedent("""\
platform_key TEXT NOT NULL, -- 平台唯一键 platform_key TEXT NOT NULL, -- 平台唯一键
credential_label TEXT NOT NULL, -- 人可读名称,例如 DeepSeek main API key credential_label TEXT NOT NULL, -- 人可读名称,例如 DeepSeek main API key
credential_type TEXT NOT NULL, -- api_key / password / bearer_token / oauth_client / refresh_token / browser_profile / note credential_type TEXT NOT NULL, -- api_key / password / bearer_token / oauth_client / refresh_token / browser_profile / note
secret_storage TEXT NOT NULL, -- none / env / windows_credential secret_storage TEXT NOT NULL, -- none / env / windows_credential / local_encrypted
secret_ref TEXT, -- env 时为环境变量名windows_credential 时为 target namenone 时为空 secret_ref TEXT, -- env 时为环境变量名windows_credential 时为 target namenone/local_encrypted 时为空
secret_mask TEXT, -- 打码展示,例如 sk-****abcd不能反推出原文 secret_mask TEXT, -- 打码展示,例如 sk-****abcd不能反推出原文
secret_ciphertext TEXT, -- secret_storage='local_encrypted' 时为 Fernet 密文;其它后端为 NULL
expires_at INTEGER, -- 过期时间Unix 秒 UTC可空 expires_at INTEGER, -- 过期时间Unix 秒 UTC可空
status TEXT NOT NULL DEFAULT 'active', -- active / disabled / expired / needs_rotation status TEXT NOT NULL DEFAULT 'active', -- active / disabled / expired / needs_rotation
last_used_at INTEGER, -- 最近使用时间 last_used_at INTEGER, -- 最近使用时间
@@ -193,7 +194,8 @@ def migrate_v1_to_v2(conn: sqlite3.Connection) -> None:
from db.platform_defaults import PLATFORM_DEFAULT_AUTH_STRATEGY from db.platform_defaults import PLATFORM_DEFAULT_AUTH_STRATEGY
cur = conn.cursor() cur = conn.cursor()
if _schema_meta_get(conn, "schema_version") == "2": sv = _schema_meta_get(conn, "schema_version")
if sv == "2" or (sv and sv.startswith("2.")):
return return
n_platform_updates = 0 n_platform_updates = 0
@@ -261,9 +263,30 @@ def migrate_v1_to_v2(conn: sqlite3.Connection) -> None:
) )
def migrate_v2_to_v2_1(conn: sqlite3.Connection) -> None:
"""v2 → v2.1: credentials 表加 secret_ciphertext 列。幂等。"""
cur = conn.cursor()
if _schema_meta_get(conn, "schema_version") == "2.1":
return
if column_exists(conn, "credentials", "secret_ciphertext"):
_schema_meta_upsert(conn, "schema_version", "2.1")
conn.commit()
return
cur.execute("ALTER TABLE credentials ADD COLUMN secret_ciphertext TEXT")
_schema_meta_upsert(conn, "schema_version", "2.1")
conn.commit()
print(
"[migration] account-manager v2→v2.1: credentials.secret_ciphertext column added.",
file=sys.stderr,
)
def ensure_schema(conn: sqlite3.Connection) -> None: def ensure_schema(conn: sqlite3.Connection) -> None:
"""CREATE TABLE/INDEX IF NOT EXISTS, then run migrate_v1_to_v2.""" """CREATE TABLE/INDEX IF NOT EXISTS, then run migrate_v1_to_v2."""
cur = conn.cursor() cur = conn.cursor()
cur.executescript(ALL_DDL) cur.executescript(ALL_DDL)
conn.commit() conn.commit()
migrate_v1_to_v2(conn) migrate_v1_to_v2(conn)
migrate_v2_to_v2_1(conn)