From 155f60cdd9d2419e9b611d8c45ce63384972ea8e Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Tue, 15 Sep 2026 00:27:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(sync):=20=E6=94=AF=E6=8C=81=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E5=AE=9E=E4=BE=8B=E5=88=9B=E5=BB=BA=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server sync/README.md | 6 ++++++ server sync/sync_server/__main__.py | 11 ++++++++++- server sync/sync_server/database.py | 9 ++++++--- server sync/tests/test_protocol.py | 12 ++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/server sync/README.md b/server sync/README.md index c404bdf..02e6273 100644 --- a/server sync/README.md +++ b/server sync/README.md @@ -31,6 +31,12 @@ uv run pytest `0.0.0.0` 并直接开放测试端口;该模式不作为生产发布配置。 6. 检查 `/health`、`/ready` 及经过授权的上传/读取;`/ready` 探测数据库 schema、staging 读写和对象存储测试前缀。 +如需让保留旧数据的升级实例执行一次首次设置,可运行下列命令。它只新增临时管理员,不删除旧账户、Vault 或对象;命令输出的随机密码在固定前也会随服务重启而失效。 + +```powershell +docker compose run --rm sync /service/.venv/bin/python -m sync_server bootstrap-user +``` + `initialize` 命令已通过真实 PostgreSQL/MinIO 的空实例与重复运行验证,并由 Compose 的一次性服务调用。MinIO 同步账号仍须由管理员创建并限制到 `opennexus` Bucket,`.env` 中的 root 与同步凭据必须不同。 2026-09-08 已在独立 Docker 项目完成真实 PostgreSQL/MinIO 双 worker 测试部署,修正基础镜像中的 `sync` 系统用户名冲突。测试专用 HTTP 地址、故障检查、完整验收缺口与运维入口见[验收报告](../docs/development/OpenNexus验收报告-2026-09-08.md)。S-07 已在原生 PostgreSQL 17.11/MinIO 实例完成 1 GiB/10,000 文件的删除源实例与空实例恢复;当前机器没有 Docker CLI,因此修改后的 Compose 编排仍需在发布环境复演,生产 TLS 也仍是独立发布门。 diff --git a/server sync/sync_server/__main__.py b/server sync/sync_server/__main__.py index c4ae150..0ae006e 100644 --- a/server sync/sync_server/__main__.py +++ b/server sync/sync_server/__main__.py @@ -24,7 +24,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument( "command", - choices=["serve", "initialize", "migrate", "create-user", "cleanup-uploads", "backup", "restore"], + choices=["serve", "initialize", "migrate", "create-user", "bootstrap-user", "cleanup-uploads", "backup", "restore"], ) parser.add_argument("--workers", type=int, choices=[1, 2], default=2) parser.add_argument("--username") @@ -80,6 +80,15 @@ def main(): elif args.command == "create-user": db.migrate() db.add_user(args.username or input("用户名: "), getpass.getpass("密码(至少12字符): ")) + elif args.command == "bootstrap-user": + db.migrate() + bootstrap = db.prepare_bootstrap_user(force=True) + print(json.dumps({ + "event": "SYNC_BOOTSTRAP_CREDENTIALS", + "username": bootstrap["username"], + "password": bootstrap["password"], + "must_change_credentials": True, + }), flush=True) elif args.command == "serve": db.migrate() bootstrap = db.prepare_bootstrap_user() diff --git a/server sync/sync_server/database.py b/server sync/sync_server/database.py index 00eb477..3b2a9fb 100644 --- a/server sync/sync_server/database.py +++ b/server sync/sync_server/database.py @@ -68,7 +68,7 @@ class Database: conn.execute(text("INSERT INTO users VALUES (:id,:name,:password)"), {"id": secrets.token_hex(16), "name": username, "password": password_hash(password)}) - def prepare_bootstrap_user(self): + def prepare_bootstrap_user(self, *, force=False): """在账户尚未固定时生成本次服务启动专用的临时密码。""" password = secrets.token_urlsafe(24) with self.transaction() as conn: @@ -78,11 +78,14 @@ class Database: run(conn, "DELETE FROM bootstrap_state") state = None if not state: - if row(conn, "SELECT id FROM users LIMIT 1"): + if row(conn, "SELECT id FROM users LIMIT 1") and not force: return None user_id = secrets.token_hex(16) + username = "admin" + if row(conn, "SELECT id FROM users WHERE username=:name", name=username): + username = "bootstrap-admin-" + secrets.token_hex(3) run(conn, "INSERT INTO users VALUES (:id,:name,:password)", - id=user_id, name="admin", password=password_hash(password)) + id=user_id, name=username, password=password_hash(password)) run(conn, "INSERT INTO bootstrap_state VALUES (:user,:created)", user=user_id, created=int(time.time())) else: diff --git a/server sync/tests/test_protocol.py b/server sync/tests/test_protocol.py index a9f5f27..f84150c 100644 --- a/server sync/tests/test_protocol.py +++ b/server sync/tests/test_protocol.py @@ -25,6 +25,18 @@ def env(tmp_path): db.engine.dispose() +def test_upgrade_can_add_bootstrap_without_removing_existing_accounts(tmp_path): + db = Database("sqlite:///" + str(tmp_path / "upgrade.db")) + db.migrate() + db.add_user("existing", "existing-account-password") + assert db.prepare_bootstrap_user() is None + bootstrap = db.prepare_bootstrap_user(force=True) + assert bootstrap["username"] == "admin" + with db.transaction() as conn: + assert conn.exec_driver_sql("SELECT COUNT(*) FROM users").scalar() == 2 + db.engine.dispose() + + def session(client, user="alice"): response = client.post("/sync/v1/auth/sessions", json={"username": user, "password": "controlled-fixture-password", "device_name": "测试设备"}) assert response.status_code == 200, response.text