Skip to content
4 changes: 4 additions & 0 deletions deploy/compose/.env.enterprise-sso.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ CF_PROVIDER_ADMIN_STARTUP_SYNC_ENABLED=false
# Optional: require one known business administrator account in the signed roster.
# This is a health assertion only; it never grants privileges by username alone.
CF_PROVIDER_ADMIN_EXPECTED_ACCOUNT=

# Enterprise org/role catalog sync (existing cf-worker, every 10 minutes).
# Does not create users or refresh GroupUser during ordinary permission checks.
CF_DIRECTORY_SYNC_INTERVAL_SECONDS=600
44 changes: 21 additions & 23 deletions deploy/compose/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ services:
CF_PROVIDER_SSO_DIRECTORY: ${CF_PROVIDER_SSO_DIRECTORY:-}
CF_SSO_GROUP_OWNER: ${CF_SSO_GROUP_OWNER:-}
CF_SSO_SYNC_INTERVAL: ${CF_SSO_SYNC_INTERVAL:-600}
CF_DIRECTORY_SYNC_INTERVAL_SECONDS: ${CF_DIRECTORY_SYNC_INTERVAL_SECONDS:-600}
CF_SSO_MAX_REMOVAL_RATIO: ${CF_SSO_MAX_REMOVAL_RATIO:-0.5}
CF_SSO_DIRECTORY_STATIC: ${CF_SSO_DIRECTORY_STATIC:-}
CF_SERVICE_SSO_DIRECTORY_URL: ${CF_SERVICE_SSO_DIRECTORY_URL:-}
Expand Down Expand Up @@ -395,24 +396,13 @@ services:
condition: service_healthy
restart: unless-stopped

# Background worker. Two mutually exclusive jobs, chosen by configuration:
#
# CF_PROVIDER_SEARCH=meilisearch -> the V2 Meilisearch indexer
# (`python3 -m cloudfile_ext.v2.search.worker`, checkpoint
# /shared/search-v2-checkpoint.json). Meilisearch answers queries but never
# indexes CloudFile's derived documents itself, so this process is what
# keeps the index current: it loops every 60s, confirms each index task,
# and a failed batch keeps its checkpoint for retry.
#
# otherwise -> `manage.py cf_worker`, which loops over the registry's
# periodic tasks and exits 0 when none is registered. The V2 runtime
# registers none today -- SSO directory sync is the push trigger
# POST /api/v2.1/admin/cloudfile/directory/entities/sync/ -- so this branch
# is a clean idle exit.
#
# Safe to keep in the default stack: it waits for cloudfile to be healthy, and
# restarts on failure only, so a clean idle exit stays exited instead of
# looping.
# Background worker. The existing cf_worker handles periodic tasks (now
# including a 10-minute enterprise org/role catalog sync). The optional
# Meilisearch indexer can run alongside it in this SAME container when
# enterprise Directory is enabled: no separate scheduler service/table.
# Failures in either long-running process restart the worker container.
# If neither indexing nor periodic tasks are configured, the clean idle
# cf_worker exit stays exited (restart: on-failure).
cf-worker:
image: ${CLOUDFILE_IMAGE:-cloudfile/cloudfile:14.0.0-cf.0}
container_name: cloudfile-worker
Expand All @@ -422,12 +412,19 @@ services:
- >
/scripts/create_data_links.sh &&
if [ "$${CF_PROVIDER_SEARCH:-}" = "meilisearch" ]; then
export DJANGO_SETTINGS_MODULE=seahub.settings &&
exec /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env
python3 -m cloudfile_ext.v2.search.worker --checkpoint /shared/search-v2-checkpoint.json;
export DJANGO_SETTINGS_MODULE=seahub.settings;
if [ -n "$${CF_SERVICE_SSO_DIRECTORY_URL:-}" ]; then
/opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env python3 /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub/manage.py cf_worker &
periodic_pid=$$!;
/opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env python3 -m cloudfile_ext.v2.search.worker --checkpoint /shared/search-v2-checkpoint.json &
search_pid=$$!;
trap 'kill "$${periodic_pid}" "$${search_pid}" 2>/dev/null || true' EXIT TERM INT;
wait -n "$${periodic_pid}" "$${search_pid}";
exit 1;
fi;
exec /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env python3 -m cloudfile_ext.v2.search.worker --checkpoint /shared/search-v2-checkpoint.json;
fi;
exec /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env
python3 /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub/manage.py cf_worker
exec /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub.sh python-env python3 /opt/seafile/$${SEAFILE_SERVER}-$${SEAFILE_VERSION}/seahub/manage.py cf_worker
volumes:
- ./data/seafile:/shared
environment:
Expand Down Expand Up @@ -521,6 +518,7 @@ services:
CF_PROVIDER_SSO_DIRECTORY: ${CF_PROVIDER_SSO_DIRECTORY:-}
CF_SSO_GROUP_OWNER: ${CF_SSO_GROUP_OWNER:-}
CF_SSO_SYNC_INTERVAL: ${CF_SSO_SYNC_INTERVAL:-600}
CF_DIRECTORY_SYNC_INTERVAL_SECONDS: ${CF_DIRECTORY_SYNC_INTERVAL_SECONDS:-600}
CF_SSO_MAX_REMOVAL_RATIO: ${CF_SSO_MAX_REMOVAL_RATIO:-0.5}
CF_SSO_DIRECTORY_STATIC: ${CF_SSO_DIRECTORY_STATIC:-}
CF_SERVICE_SSO_DIRECTORY_URL: ${CF_SERVICE_SSO_DIRECTORY_URL:-}
Expand Down
4 changes: 4 additions & 0 deletions scripts/scripts_cf14.0/cloudfile_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ def write_cloudfile_settings():
)

body += _settings_block_sso()
# V2 enterprise org/role catalog worker interval; one low-frequency task.
# Malformed values fail startup through the Hub's bounded setting gate.
body += 'CF_DIRECTORY_SYNC_INTERVAL_SECONDS = %r\n' % int(
get_conf('CF_DIRECTORY_SYNC_INTERVAL_SECONDS', '600'))
# 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.
Expand Down
9 changes: 8 additions & 1 deletion tests/test_worker_lifecycle.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""The cf-worker service must not restart forever after a clean idle exit.

`manage.py cf_worker` exits 0 when no capability registered a periodic task, and
the V2 runtime registers none today. With `restart: unless-stopped` that clean
the standalone V2 runtime registers none without a Directory. With `restart: unless-stopped` that clean
exit becomes an endless restart loop (observed ExitCode=0, RestartCount 455+ on
minio157), so the policy must be `on-failure`.

Expand Down Expand Up @@ -70,6 +70,13 @@ def test_worker_runs_the_meilisearch_indexer_when_it_is_the_provider(self):
self.assertIn("cloudfile_ext.v2.search.worker", command)
self.assertIn("/shared/search-v2-checkpoint.json", command)

def test_meili_mode_keeps_directory_catalog_scheduling(self):
command = "\\n".join(self.block)
self.assertIn('CF_SERVICE_SSO_DIRECTORY_URL', command)
self.assertIn('periodic_pid=$!', command)
self.assertIn('wait -n "${periodic_pid}" "${search_pid}"', command)
self.assertIn('CF_DIRECTORY_SYNC_INTERVAL_SECONDS: ${CF_DIRECTORY_SYNC_INTERVAL_SECONDS:-600}', command)

def test_worker_stays_in_the_default_stack(self):
self.assertEqual([], service_values(self.block, "profiles"))

Expand Down
Loading