diff --git a/deployment/requests/HLCC-EXACT-REPOSITORY-ACL-PERSIST-20260806.json b/deployment/requests/HLCC-EXACT-REPOSITORY-ACL-PERSIST-20260806.json deleted file mode 100644 index b7a461d..0000000 --- a/deployment/requests/HLCC-EXACT-REPOSITORY-ACL-PERSIST-20260806.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "schema": "guanghu.architecture-provision-request/v1", - "request_id": "HLCC-EXACT-REPOSITORY-ACL-PERSIST-20260806", - "target_node": "JD-FD-PRIMARY", - "architecture_id": "GLS-HLCC-009", - "module": { - "code": "HLCC-JD-CANDIDATE-01", - "name": "光湖代码频道精确仓库 ACL 持久化", - "bind": "loopback:3340,3341", - "owner": "systemd", - "unit": "hlcc-jd-candidate.service", - "run_user": "guanghu", - "writable_paths": [ - "/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1" - ], - "read_only_paths": [] - }, - "source_ref": "REPO-012:refs/heads/main", - "deployed_commit_policy": "use the exact human-merged commit and record it in the server-owned deployment receipt", - "source_paths": [ - "server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py", - "server-tools/hololake-code-channel/jd-candidate/app.ini", - "server-tools/hololake-code-channel/jd-candidate/hlcc-jd-candidate.service" - ], - "initial_provision": { - "kind": "new-architecture-unit", - "not_an_existing_action_bridge_extension": true, - "requires": [ - "leave core.sharedRepository unset to avoid setgid operations under RestrictSUIDSGID", - "apply inherited ACLs only to the exact repository objects and refs", - "grant only guanghu-authz the additional repository access", - "preserve UMask=0077 for all other code-channel state", - "keep the obsolete recursive chmod repository hook absent", - "quote the generated Forgejo custom-hook basename test", - "restart only hlcc-jd-candidate.service", - "do not alter repository content, branches, owner identity or authentication" - ] - }, - "verification": [ - "the automatic deployment receipt records DEPLOYED_AND_VERIFIED", - "the candidate subsequently reaches ready=true and mode=isolated-candidate", - "core.sharedRepository remains unset", - "objects and refs inherit the exact guanghu-authz ACL", - "a new-object multilevel branch push and delete complete without warnings", - "both code-channel and direct-receiver repository checks pass" - ], - "runtime_check": { - "url": "http://127.0.0.1:3341/health", - "expected": { - "ok": true, - "mode": "bootstrap", - "version": "16.0.1", - "code": "HLCC-JD-CANDIDATE-01", - "package_profile": "full-offline-v16.0.1" - } - }, - "rollback": [ - "restore the backed-up hlcc-jd-candidate.service", - "restart the previous immutable release", - "retain repository content and authentication state", - "use the server-owned ACL backup for manual permission recovery" - ], - "status": "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING" -} diff --git a/server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py b/server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py index 92d21cf..3a35229 100644 --- a/server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py +++ b/server-tools/hololake-code-channel/jd-candidate/hlcc-bootstrap.py @@ -18,7 +18,6 @@ import time import urllib.error import urllib.parse import urllib.request -from collections.abc import Callable VERSION = "16.0.1" @@ -483,68 +482,39 @@ def seed_fifth_domain_channel(binary: pathlib.Path) -> str: def configure_shared_channel_repository( repository: pathlib.Path = CHANNEL_REPOSITORY_PATH, - generated_hook: pathlib.Path | None = None, - acl_runner: Callable[[list[str]], None] | None = None, ) -> str: - """Share only this repository through exact inherited ACLs.""" + """Use Git's native shared-repository mode instead of a post-receive chmod.""" if not repository.is_dir(): raise RuntimeError("channel repository path unavailable") - unset_result = subprocess.run( + subprocess.run( [ "git", "--git-dir", str(repository), "config", - "--unset-all", "core.sharedRepository", + "group", ], + check=True, capture_output=True, text=True, ) - if unset_result.returncode not in {0, 5}: - raise RuntimeError("shared repository configuration removal failed") - - def apply_acl(command: list[str]) -> None: - if acl_runner: - acl_runner(command) - return - subprocess.run(command, check=True, capture_output=True, text=True) - - objects = repository / "objects" - refs = repository / "refs" - apply_acl( + configured = subprocess.run( [ - "/usr/bin/setfacl", - "-R", - "-m", - "u:guanghu-authz:rwX,m::rwx", - str(objects), - str(refs), - ] - ) - for root in (objects, refs): - for directory, child_directories, _files in os.walk(root): - apply_acl( - [ - "/usr/bin/setfacl", - "-m", - "d:u:guanghu-authz:rwx,d:m::rwx", - directory, - ] - ) - child_directories.sort() - for name, permissions in (("HEAD", "rw"), ("packed-refs", "rw"), ("config", "r")): - path = repository / name - if path.is_file(): - apply_acl( - [ - "/usr/bin/setfacl", - "-m", - f"u:guanghu-authz:{permissions}", - str(path), - ] - ) + "git", + "--git-dir", + str(repository), + "config", + "--get", + "core.sharedRepository", + ], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + if configured not in {"1", "group"}: + raise RuntimeError("shared repository configuration verification failed") obsolete_hook = ( repository @@ -560,23 +530,6 @@ def configure_shared_channel_repository( raise RuntimeError("obsolete sharing hook identity mismatch") obsolete_hook.unlink() - generated_hook = generated_hook or ( - STATE_ROOT / "data" / "data" / "home" / "hooks" / "post-receive" - ) - if not generated_hook.is_file() or generated_hook.is_symlink(): - raise RuntimeError("generated post-receive hook path unavailable") - generated_text = generated_hook.read_text(encoding="utf-8") - unsafe_test = 'if [ $(basename "${hook}") != "gitea" ]; then' - safe_test = 'if [ "$(basename "${hook}")" != "gitea" ]; then' - if unsafe_test in generated_text: - generated_hook.write_text( - generated_text.replace(unsafe_test, safe_test, 1), - encoding="utf-8", - ) - generated_hook.chmod(0o700) - elif safe_test not in generated_text: - raise RuntimeError("generated post-receive hook identity mismatch") - return "configured" diff --git a/server-tools/hololake-code-channel/jd-candidate/native.test.js b/server-tools/hololake-code-channel/jd-candidate/native.test.js index 672aced..bd6438f 100644 --- a/server-tools/hololake-code-channel/jd-candidate/native.test.js +++ b/server-tools/hololake-code-channel/jd-candidate/native.test.js @@ -48,12 +48,8 @@ assert.match(bootstrap, /access_tokens_migrated": False/); assert.match(bootstrap, /repositories_migrated": False/); assert.match(bootstrap, /delete from access_token/); assert.match(bootstrap, /core\.sharedRepository/); -assert.match(bootstrap, /"--unset-all"/); -assert.match(bootstrap, /\/usr\/bin\/setfacl/); -assert.match(bootstrap, /d:u:guanghu-authz:rwx/); +assert.match(bootstrap, /"group"/); assert.match(bootstrap, /obsolete sharing hook identity mismatch/); -assert.match(bootstrap, /generated post-receive hook identity mismatch/); -assert.match(bootstrap, /basename "\$\{hook\}"/); assert.match(bootstrap, /configure_shared_channel_repository\(\)/); assert.doesNotMatch( bootstrap, diff --git a/server-tools/hololake-code-channel/jd-candidate/test_owner_migration.py b/server-tools/hololake-code-channel/jd-candidate/test_owner_migration.py index 67e8e56..75cdf80 100644 --- a/server-tools/hololake-code-channel/jd-candidate/test_owner_migration.py +++ b/server-tools/hololake-code-channel/jd-candidate/test_owner_migration.py @@ -150,19 +150,9 @@ class SharedRepositoryTests(unittest.TestCase): "#!/bin/sh\nprintf '%s\\n' post_receive_permissions_reconciled\n", encoding="utf-8", ) - generated_hook = pathlib.Path(temporary) / "generated-post-receive" - generated_hook.write_text( - '#!/usr/bin/env bash\nif [ $(basename "${hook}") != "gitea" ]; then\n :\nfi\n', - encoding="utf-8", - ) - acl_commands = [] self.assertEqual( - MODULE.configure_shared_channel_repository( - repository, - generated_hook, - acl_commands.append, - ), + MODULE.configure_shared_channel_repository(repository), "configured", ) configured = subprocess.run( @@ -174,22 +164,12 @@ class SharedRepositoryTests(unittest.TestCase): "--get", "core.sharedRepository", ], + check=True, capture_output=True, text=True, - ) - self.assertNotEqual(configured.returncode, 0) - self.assertTrue( - any( - "d:u:guanghu-authz:rwx" in argument - for command in acl_commands - for argument in command - ) - ) + ).stdout.strip() + self.assertIn(configured, {"1", "group"}) self.assertFalse(hook.exists()) - self.assertIn( - 'if [ "$(basename "${hook}")" != "gitea" ]; then', - generated_hook.read_text(encoding="utf-8"), - ) def test_refuses_unknown_hook_content(self) -> None: with tempfile.TemporaryDirectory() as temporary: @@ -198,18 +178,9 @@ class SharedRepositoryTests(unittest.TestCase): hook = repository / "hooks" / "post-receive.d" / "guanghu-ice-heart-share" hook.parent.mkdir(parents=True) hook.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8") - generated_hook = pathlib.Path(temporary) / "generated-post-receive" - generated_hook.write_text( - '#!/usr/bin/env bash\nif [ $(basename "${hook}") != "gitea" ]; then\n :\nfi\n', - encoding="utf-8", - ) with self.assertRaisesRegex(RuntimeError, "identity mismatch"): - MODULE.configure_shared_channel_repository( - repository, - generated_hook, - lambda _command: None, - ) + MODULE.configure_shared_channel_repository(repository) self.assertTrue(hook.exists()) diff --git a/server-tools/lake-lamp-authz/README.md b/server-tools/lake-lamp-authz/README.md index 576afd3..49f3f1d 100644 --- a/server-tools/lake-lamp-authz/README.md +++ b/server-tools/lake-lamp-authz/README.md @@ -125,11 +125,9 @@ node server-tools/lake-lamp-authz/authorize-repo-push.js \ `safe.directory`,不得使用通配符。 光湖代码频道本身保留 `UMask=0077`,以免放宽数据库和其他状态目录。对需要由直达 -接收器共同读写的精确裸仓库不设置 `core.sharedRepository`,而只在该仓库的 -`objects` 与 `refs` 上为 `guanghu-authz` 设置继承 ACL。这样新对象和多级分支目录 -从创建时就允许精确接收账户访问,不要求安全沙箱禁止的 setgid 操作,也不扩大到代码 -频道数据库或其他状态。不要安装推送后递归 `chmod` 的钩子,也不要为了共享一个裸仓库 -而修改整个代码频道服务的 UMask。 +接收器共同读写的精确裸仓库设置 Git 原生 `core.sharedRepository=group`,让新建的 +`objects` 与 `refs` 从写入时就继承专用共享组权限。不要安装推送后递归 `chmod` 的 +钩子,也不要为了共享一个裸仓库而修改整个代码频道服务的 UMask。 部署后用当前主分支生成无变化验收 bundle,经 `receiveBundle` 完整执行一次;验收前后 主分支 SHA 必须一致,并且代码频道账户与授权服务账户执行 `git fsck` 均通过。