From 0aa0a9bbf8c5a39f69f453e4d4d55bbdf162a928 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:09:58 +0900 Subject: [PATCH 01/20] feat(startup): bounded one-shot admin sync without blocking Seafile service --- .../scripts_cf14.0/cloudfile_admin_startup.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 scripts/scripts_cf14.0/cloudfile_admin_startup.py diff --git a/scripts/scripts_cf14.0/cloudfile_admin_startup.py b/scripts/scripts_cf14.0/cloudfile_admin_startup.py new file mode 100644 index 00000000..196538c7 --- /dev/null +++ b/scripts/scripts_cf14.0/cloudfile_admin_startup.py @@ -0,0 +1,37 @@ +"""One bounded CloudFile administrator sync after a normal Seafile startup. + +Do not run from Seahub/Gunicorn worker initialization: those launch repeatedly +and must never create another background reconciliation loop. +""" +import os +import shlex +import subprocess + + +def run_provider_admin_sync(seahub_script, manage_script, *, non_root=False, + enabled=None, runner=subprocess.run): + if enabled is None: + enabled = os.getenv('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true' + if enabled is not True: + return 'SKIPPED' + command = [seahub_script, 'python-env', 'python3', manage_script, + 'cf_sync_provider_admins'] + if non_root: + command = ['su', '-p', 'seafile', '-c', shlex.join(command)] + try: + # Only one process per container start. Provider request has its own + # 5s bound; leave some time for CE SQL/RPC. Never block the file service + # if an external enterprise Directory is unavailable. + result = runner(command, capture_output=True, text=True, timeout=30, + check=False) + if result.returncode: + print('CF_ADMIN_SYNC FAILED (nonfatal): command returned %d' + % result.returncode, flush=True) + return 'FAILED' + # The command prints counts only. Do not leak accounts or credentials. + line = (result.stdout or '').strip().splitlines() + print(line[-1] if line else 'CF_ADMIN_SYNC COMPLETED', flush=True) + return 'APPLIED' + except (OSError, subprocess.SubprocessError): + print('CF_ADMIN_SYNC FAILED (nonfatal): command unavailable', flush=True) + return 'FAILED' From 3f549697f300c78e0fd3d71382168b0099e7f351 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:10:16 +0900 Subject: [PATCH 02/20] feat(startup): call one-shot native admin synchronization after Seafile launches --- scripts/scripts_14.0/start.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/scripts_14.0/start.py b/scripts/scripts_14.0/start.py index 6fa80287..457a47cb 100755 --- a/scripts/scripts_14.0/start.py +++ b/scripts/scripts_14.0/start.py @@ -27,6 +27,7 @@ from cloudfile import write_settings as write_cloudfile_settings from cloudfile import write_policy_worker_hooks from cloudfile_schema import prepare_fresh_schema, initialize_fresh_schema, verify_development_schema +from cloudfile_admin_startup import run_provider_admin_sync shared_seafiledir = '/shared/seafile' @@ -112,6 +113,12 @@ def main(): if exists(password_file): os.unlink(password_file) + # One low-frequency sync at service start, never in Gunicorn workers or + # every HTTP request. The Provider can be offline; warn and keep CE running. + run_provider_admin_sync(get_script('seahub.sh'), + join(installdir, 'seahub', 'manage.py'), + non_root=non_root) + print('seafile server is running now.') try: watch_controller() From ddeee1630f559db8fc0f9d2d0361f71da927d1d0 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:11:16 +0900 Subject: [PATCH 03/20] feat(config): generate only two startup admin sync settings, default disabled --- scripts/scripts_cf14.0/cloudfile_bootstrap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/scripts_cf14.0/cloudfile_bootstrap.py b/scripts/scripts_cf14.0/cloudfile_bootstrap.py index 56607695..47dd0e4c 100644 --- a/scripts/scripts_cf14.0/cloudfile_bootstrap.py +++ b/scripts/scripts_cf14.0/cloudfile_bootstrap.py @@ -196,6 +196,11 @@ def write_cloudfile_settings(): ) body += _settings_block_sso() + # Low-frequency native admin sync is separate from the old SSO worker. + # It must remain off in Standalone CE and must never start an HTTP poll. + body += 'CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED = %r\\n' % ( + get_conf('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true') + body += 'CF_PROVIDER_ADMIN_SOURCE = %r\\n' % get_conf('CF_PROVIDER_ADMIN_SOURCE', '') body += _settings_block_search() body += _settings_block_v04_preview_embed() body += _settings_block_external_sources() From 14f871be9cf0bbd100767681b9b6e469c6e71b07 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:12:08 +0900 Subject: [PATCH 04/20] fix(config): write actual newline-delimited generated admin settings --- scripts/scripts_cf14.0/cloudfile_bootstrap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/scripts_cf14.0/cloudfile_bootstrap.py b/scripts/scripts_cf14.0/cloudfile_bootstrap.py index 47dd0e4c..a604a2dd 100644 --- a/scripts/scripts_cf14.0/cloudfile_bootstrap.py +++ b/scripts/scripts_cf14.0/cloudfile_bootstrap.py @@ -198,9 +198,9 @@ def write_cloudfile_settings(): body += _settings_block_sso() # Low-frequency native admin sync is separate from the old SSO worker. # It must remain off in Standalone CE and must never start an HTTP poll. - body += 'CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED = %r\\n' % ( + body += 'CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED = %r\n' % ( get_conf('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true') - body += 'CF_PROVIDER_ADMIN_SOURCE = %r\\n' % get_conf('CF_PROVIDER_ADMIN_SOURCE', '') + body += 'CF_PROVIDER_ADMIN_SOURCE = %r\n' % get_conf('CF_PROVIDER_ADMIN_SOURCE', '') body += _settings_block_search() body += _settings_block_v04_preview_embed() body += _settings_block_external_sources() From 5d899e5dce82b0b7d9de59325099214718fe9cfd Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:12:35 +0900 Subject: [PATCH 05/20] feat(compose): opt-in startup admin roster sync on main CloudFile service --- deploy/compose/docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deploy/compose/docker-compose.yml b/deploy/compose/docker-compose.yml index 63200be4..25bc275a 100644 --- a/deploy/compose/docker-compose.yml +++ b/deploy/compose/docker-compose.yml @@ -301,6 +301,9 @@ services: CF_SSO_DIRECTORY_STATIC: ${CF_SSO_DIRECTORY_STATIC:-} CF_SERVICE_SSO_DIRECTORY_URL: ${CF_SERVICE_SSO_DIRECTORY_URL:-} CF_SERVICE_SSO_DIRECTORY_SECRET: ${CF_SERVICE_SSO_DIRECTORY_SECRET:-} + # One bounded startup-only enterprise admin sync, no periodic/request polling. + CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED: ${CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED:-false} + CF_PROVIDER_ADMIN_SOURCE: ${CF_PROVIDER_ADMIN_SOURCE:-} # Default to v2 on both app and worker; never auto-downgrade failed auth. CF_SERVICE_SSO_DIRECTORY_AUTH_MODE: ${CF_SERVICE_SSO_DIRECTORY_AUTH_MODE:-v2} CF_SERVICE_SSO_DIRECTORY_KEY_ID: ${CF_SERVICE_SSO_DIRECTORY_KEY_ID:-} From 0abe9507237adf4d561c6b5bc0b336d2efccda4b Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:12:56 +0900 Subject: [PATCH 06/20] docs(config): document optional startup-only provider admin sync --- deploy/compose/.env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index 34543146..3a4e8b92 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -344,6 +344,12 @@ CF_SERVICE_SSO_DIRECTORY_SECRET= CF_SERVICE_SSO_DIRECTORY_AUTH_MODE=v2 CF_SERVICE_SSO_DIRECTORY_KEY_ID= CF_SERVICE_SSO_DIRECTORY_SERVICE_ID=cloudfile +# Startup-only administrator roster: no per-request Provider checks. +# Enable after verifying the complete machine-authenticated admin list and +# operator ownership of CE default_admin in the enterprise namespace. +CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false +CF_PROVIDER_ADMIN_SOURCE= + # UID-scoped incremental administrative sync: safe preview by default. CF_SSO_UID_DELTA_APPLY_ENABLED=false CF_SSO_UID_DELTA_MAX_REMOVALS=0 From 9aa5250c70d7df2d1426f8c4ca97a2c5cbbd20e2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:13:00 +0900 Subject: [PATCH 07/20] docs(config): document optional startup-only provider admin sync --- deploy/compose/.env.etech-sso.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/compose/.env.etech-sso.example b/deploy/compose/.env.etech-sso.example index 11e45192..5d1c5574 100644 --- a/deploy/compose/.env.etech-sso.example +++ b/deploy/compose/.env.etech-sso.example @@ -2,3 +2,8 @@ # Generic OAuth defaults stay provider-neutral. No credentials belong in this file. CF_SSO_OAUTH_LOGIN_ID_CLAIM=userId CF_SSO_EAP_PROFILE_IDENTITY=false +# Optional: enable ONLY after the EAP Directory /administrators machine endpoint +# and CE admin ownership have been verified. Uses existing Directory URL/key/secret. +# A failed one-time fetch warns without preventing the CloudFile service startup. +CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false + From 336edb4edb57d3ea1896b438265cf4119b7d8dd2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:13:45 +0900 Subject: [PATCH 08/20] test(startup): verify one invocation, time bound and nonfatal Directory failure --- tests/test_admin_startup_hook.py | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/test_admin_startup_hook.py diff --git a/tests/test_admin_startup_hook.py b/tests/test_admin_startup_hook.py new file mode 100644 index 00000000..b441414a --- /dev/null +++ b/tests/test_admin_startup_hook.py @@ -0,0 +1,56 @@ +"""Cheap startup-hook checks; no Docker image, database or network required.""" +import importlib.util +from pathlib import Path +from types import SimpleNamespace +import subprocess +import unittest +from unittest.mock import Mock + +FILE = (Path(__file__).resolve().parents[1] / 'scripts' / + 'scripts_cf14.0' / 'cloudfile_admin_startup.py') +spec = importlib.util.spec_from_file_location('admin_sync_startup', FILE) +admin_sync = importlib.util.module_from_spec(spec) +spec.loader.exec_module(admin_sync) + + +class StartupSyncHookTests(unittest.TestCase): + def test_disabled_never_spawns_an_process(self): + spawn = Mock(side_effect=AssertionError('disabled')) + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=False, runner=spawn), + 'SKIPPED') + spawn.assert_not_called() + + def test_enabled_spawns_once_with_bounded_timeout(self): + spawn = Mock(return_value=SimpleNamespace( + returncode=0, stdout='CF_ADMIN_SYNC APPLIED added=1 removed=0 unchanged=0\n')) + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=True, runner=spawn), 'APPLIED') + args, kw = spawn.call_args + self.assertEqual(args[0][-2:], ['/seahub/manage.py', 'cf_sync_provider_admins']) + self.assertEqual(kw['timeout'], 30) + self.assertFalse(kw['check']) + self.assertEqual(spawn.call_count, 1) + + def test_failed_provider_never_aborts_container_startup(self): + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=True, + runner=Mock(return_value=SimpleNamespace(returncode=1, stdout=''))), + 'FAILED') + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=True, + runner=Mock(side_effect=subprocess.TimeoutExpired('sync', 30))), + 'FAILED') + + def test_nonroot_command_is_one_safely_quoted_shell_command(self): + spawn = Mock(return_value=SimpleNamespace(returncode=0, stdout='ok')) + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=True, + non_root=True, runner=spawn), 'APPLIED') + args = spawn.call_args.args[0] + self.assertEqual(args[:4], ['su', '-p', 'seafile', '-c']) + self.assertIn('cf_sync_provider_admins', args[-1]) + + +if __name__ == '__main__': + unittest.main() From cf07f93bb425d70ab442c60aad882e115e716cc7 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:16:54 +0900 Subject: [PATCH 09/20] fix(ci): remove extra blank line from Etech environment example --- deploy/compose/.env.etech-sso.example | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/compose/.env.etech-sso.example b/deploy/compose/.env.etech-sso.example index 5d1c5574..89cf82bc 100644 --- a/deploy/compose/.env.etech-sso.example +++ b/deploy/compose/.env.etech-sso.example @@ -6,4 +6,3 @@ CF_SSO_EAP_PROFILE_IDENTITY=false # and CE admin ownership have been verified. Uses existing Directory URL/key/secret. # A failed one-time fetch warns without preventing the CloudFile service startup. CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false - From 0ddffdebb5c68137f666e76350ef1ecaf8df592e Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:17:12 +0900 Subject: [PATCH 10/20] ci(startup): test one-shot sync, timeout and provider-outage isolation --- .github/workflows/dev.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 53a4d90c..0bd74d5d 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -44,6 +44,9 @@ jobs: [[ ! -f "$path" ]] || bash -n "$path" done < <(git diff --name-only HEAD^ HEAD -- '*.sh') + - name: Validate one-shot startup admin hook (pure Python) + run: python3 -m unittest discover -s tests -p test_admin_startup_hook.py -q + - name: Enforce current V2 ACL documentation contract run: python3 tests/test_v2_authorization_contract.py From 6f207ff9a5758fbd4bd7ab74a6e24061a57124e2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:19:09 +0900 Subject: [PATCH 11/20] fix(startup): reuse EAP Directory when legacy full SSO sync is disabled --- scripts/scripts_cf14.0/cloudfile_bootstrap.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/scripts_cf14.0/cloudfile_bootstrap.py b/scripts/scripts_cf14.0/cloudfile_bootstrap.py index a604a2dd..5fc4eaf5 100644 --- a/scripts/scripts_cf14.0/cloudfile_bootstrap.py +++ b/scripts/scripts_cf14.0/cloudfile_bootstrap.py @@ -196,6 +196,14 @@ def write_cloudfile_settings(): ) body += _settings_block_sso() + # EAP may use the existing OAuth/SSO bridge with the legacy full + # group-synchronizer disabled. When the new one-shot administrator sync is + # explicitly enabled, still expose its trusted Directory connection. + if (not cf_enabled('CF_ENABLE_SSO') and + get_conf('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true'): + body += 'CF_DIRECTORY_URL = %r\n' % get_conf('CF_SERVICE_SSO_DIRECTORY_URL', '') + body += 'CF_DIRECTORY_KEY_ID = %r\n' % get_conf('CF_SERVICE_SSO_DIRECTORY_KEY_ID', '') + body += 'CF_DIRECTORY_SECRET = %r\n' % get_conf('CF_SERVICE_SSO_DIRECTORY_SECRET', '') # Low-frequency native admin sync is separate from the old SSO worker. # It must remain off in Standalone CE and must never start an HTTP poll. body += 'CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED = %r\n' % ( From afe662f5d56370d81e725d2cf81a73d9a0154200 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:46:28 +0900 Subject: [PATCH 12/20] fix(admin): expose explicit safe native admin revocation scope --- scripts/scripts_cf14.0/cloudfile_bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/scripts_cf14.0/cloudfile_bootstrap.py b/scripts/scripts_cf14.0/cloudfile_bootstrap.py index 5fc4eaf5..2de0988f 100644 --- a/scripts/scripts_cf14.0/cloudfile_bootstrap.py +++ b/scripts/scripts_cf14.0/cloudfile_bootstrap.py @@ -209,6 +209,7 @@ def write_cloudfile_settings(): body += 'CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED = %r\n' % ( get_conf('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true') body += 'CF_PROVIDER_ADMIN_SOURCE = %r\n' % get_conf('CF_PROVIDER_ADMIN_SOURCE', '') + body += 'CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS = %r\n' % get_conf('CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS', '') body += _settings_block_search() body += _settings_block_v04_preview_embed() body += _settings_block_external_sources() From 40c3c4ef9fe71d93f74d8d2175ca99d2755ede19 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:46:33 +0900 Subject: [PATCH 13/20] fix(admin): expose explicit safe native admin revocation scope --- deploy/compose/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/compose/docker-compose.yml b/deploy/compose/docker-compose.yml index 25bc275a..789b0897 100644 --- a/deploy/compose/docker-compose.yml +++ b/deploy/compose/docker-compose.yml @@ -304,6 +304,7 @@ services: # One bounded startup-only enterprise admin sync, no periodic/request polling. CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED: ${CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED:-false} CF_PROVIDER_ADMIN_SOURCE: ${CF_PROVIDER_ADMIN_SOURCE:-} + CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS: ${CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS:-} # Default to v2 on both app and worker; never auto-downgrade failed auth. CF_SERVICE_SSO_DIRECTORY_AUTH_MODE: ${CF_SERVICE_SSO_DIRECTORY_AUTH_MODE:-v2} CF_SERVICE_SSO_DIRECTORY_KEY_ID: ${CF_SERVICE_SSO_DIRECTORY_KEY_ID:-} From 4c9d2b377eb864642dda6bf85771d7fa25c1a2ba Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:46:36 +0900 Subject: [PATCH 14/20] fix(admin): expose explicit safe native admin revocation scope --- deploy/compose/.env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index 3a4e8b92..285d5497 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -349,6 +349,8 @@ CF_SERVICE_SSO_DIRECTORY_SERVICE_ID=cloudfile # operator ownership of CE default_admin in the enterprise namespace. CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false CF_PROVIDER_ADMIN_SOURCE= +# Optional exact CE usernames owned by Provider for automatic revocation; empty is safest. +CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS= # UID-scoped incremental administrative sync: safe preview by default. CF_SSO_UID_DELTA_APPLY_ENABLED=false From 032c7157254bdee34cacfb6f079b8cb96bd4e498 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:52:19 +0900 Subject: [PATCH 15/20] feat(admin): expose optional Etech admin account compatibility check --- scripts/scripts_cf14.0/cloudfile_bootstrap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/scripts_cf14.0/cloudfile_bootstrap.py b/scripts/scripts_cf14.0/cloudfile_bootstrap.py index 2de0988f..22d67ff7 100644 --- a/scripts/scripts_cf14.0/cloudfile_bootstrap.py +++ b/scripts/scripts_cf14.0/cloudfile_bootstrap.py @@ -210,6 +210,7 @@ def write_cloudfile_settings(): get_conf('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED', 'false').lower() == 'true') body += 'CF_PROVIDER_ADMIN_SOURCE = %r\n' % get_conf('CF_PROVIDER_ADMIN_SOURCE', '') body += 'CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS = %r\n' % get_conf('CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS', '') + body += 'CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT = %r\n' % get_conf('CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT', '') body += _settings_block_search() body += _settings_block_v04_preview_embed() body += _settings_block_external_sources() From 61d543a8fecd8f921e4a5bbdaa79dbbb9f5b8b73 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:52:23 +0900 Subject: [PATCH 16/20] feat(admin): expose optional Etech admin account compatibility check --- deploy/compose/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/compose/docker-compose.yml b/deploy/compose/docker-compose.yml index 789b0897..da83e896 100644 --- a/deploy/compose/docker-compose.yml +++ b/deploy/compose/docker-compose.yml @@ -305,6 +305,7 @@ services: CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED: ${CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED:-false} CF_PROVIDER_ADMIN_SOURCE: ${CF_PROVIDER_ADMIN_SOURCE:-} CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS: ${CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS:-} + CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT: ${CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT:-} # Default to v2 on both app and worker; never auto-downgrade failed auth. CF_SERVICE_SSO_DIRECTORY_AUTH_MODE: ${CF_SERVICE_SSO_DIRECTORY_AUTH_MODE:-v2} CF_SERVICE_SSO_DIRECTORY_KEY_ID: ${CF_SERVICE_SSO_DIRECTORY_KEY_ID:-} From 56790e31a1b0a770cc535b323fb1459bb50e0965 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:52:26 +0900 Subject: [PATCH 17/20] feat(admin): expose optional Etech admin account compatibility check --- deploy/compose/.env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index 285d5497..885e3535 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -351,6 +351,8 @@ CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false CF_PROVIDER_ADMIN_SOURCE= # Optional exact CE usernames owned by Provider for automatic revocation; empty is safest. CF_PROVIDER_ADMIN_MANAGED_ACCOUNTS= +# Optional expected trusted admin account; not an elevation by username. +CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT= # UID-scoped incremental administrative sync: safe preview by default. CF_SSO_UID_DELTA_APPLY_ENABLED=false From 97fc04f975d6c0764a61ae88e3a006acf54a0f97 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 16:52:29 +0900 Subject: [PATCH 18/20] feat(admin): expose optional Etech admin account compatibility check --- deploy/compose/.env.etech-sso.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/compose/.env.etech-sso.example b/deploy/compose/.env.etech-sso.example index 89cf82bc..9f332bbc 100644 --- a/deploy/compose/.env.etech-sso.example +++ b/deploy/compose/.env.etech-sso.example @@ -6,3 +6,8 @@ CF_SSO_EAP_PROFILE_IDENTITY=false # and CE admin ownership have been verified. Uses existing Directory URL/key/secret. # A failed one-time fetch warns without preventing the CloudFile service startup. CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false + +# Etech's business administrator is `admin`, authenticated through the +# trusted Provider and existing SSO. This requires it in the signed roster; +# it never grants privileges just because the username matches. +CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=admin From 25f937f807432a389ed036d2e730744639f38033 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 17:13:00 +0900 Subject: [PATCH 19/20] fix(admin): require explicit applied receipt before reporting startup sync success --- scripts/scripts_cf14.0/cloudfile_admin_startup.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/scripts_cf14.0/cloudfile_admin_startup.py b/scripts/scripts_cf14.0/cloudfile_admin_startup.py index 196538c7..8f85fa13 100644 --- a/scripts/scripts_cf14.0/cloudfile_admin_startup.py +++ b/scripts/scripts_cf14.0/cloudfile_admin_startup.py @@ -28,9 +28,16 @@ def run_provider_admin_sync(seahub_script, manage_script, *, non_root=False, print('CF_ADMIN_SYNC FAILED (nonfatal): command returned %d' % result.returncode, flush=True) return 'FAILED' - # The command prints counts only. Do not leak accounts or credentials. - line = (result.stdout or '').strip().splitlines() - print(line[-1] if line else 'CF_ADMIN_SYNC COMPLETED', flush=True) + # A zero exit code is not sufficient: the management command may + # explicitly SKIP because its generated settings were not enabled. + # Never claim successful sync without the command's applied marker. + lines = (result.stdout or '').strip().splitlines() + applied = [line.strip() for line in lines + if line.strip().startswith('CF_ADMIN_SYNC APPLIED added=')] + if len(applied) != 1: + print('CF_ADMIN_SYNC FAILED (nonfatal): no applied receipt', flush=True) + return 'FAILED' + print(applied[0], flush=True) return 'APPLIED' except (OSError, subprocess.SubprocessError): print('CF_ADMIN_SYNC FAILED (nonfatal): command unavailable', flush=True) From eb157992b94f36450377d9048c9db0481a4083fd Mon Sep 17 00:00:00 2001 From: servicememeai Date: Sat, 24 Oct 2026 17:13:22 +0900 Subject: [PATCH 20/20] test(admin): prevent false-success startup status and keep Etech admin opt-in --- tests/test_admin_startup_hook.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_admin_startup_hook.py b/tests/test_admin_startup_hook.py index b441414a..c5d09df8 100644 --- a/tests/test_admin_startup_hook.py +++ b/tests/test_admin_startup_hook.py @@ -42,8 +42,23 @@ def test_failed_provider_never_aborts_container_startup(self): runner=Mock(side_effect=subprocess.TimeoutExpired('sync', 30))), 'FAILED') + def test_zero_exit_without_applied_receipt_is_failure_not_false_success(self): + for stdout in ('', 'CF_ADMIN_SYNC SKIPPED (not configured)', + 'unrelated successful output'): + with self.subTest(stdout=stdout): + self.assertEqual(admin_sync.run_provider_admin_sync( + '/seahub.sh', '/seahub/manage.py', enabled=True, + runner=Mock(return_value=SimpleNamespace( + returncode=0, stdout=stdout))), 'FAILED') + + def test_etech_default_admin_compatibility_remains_opt_in(self): + root = Path(__file__).resolve().parents[1] + env = (root / 'deploy/compose/.env.etech-sso.example').read_text() + self.assertIn('CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=admin', env) + self.assertIn('CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false', env) + def test_nonroot_command_is_one_safely_quoted_shell_command(self): - spawn = Mock(return_value=SimpleNamespace(returncode=0, stdout='ok')) + spawn = Mock(return_value=SimpleNamespace(returncode=0, stdout='CF_ADMIN_SYNC APPLIED added=1 removed=0 unchanged=0')) self.assertEqual(admin_sync.run_provider_admin_sync( '/seahub.sh', '/seahub/manage.py', enabled=True, non_root=True, runner=spawn), 'APPLIED')