diff --git a/tests/test_v2_native_config.py b/docs/history/storage-build-20261020/tests/test_v2_native_config.py similarity index 97% rename from tests/test_v2_native_config.py rename to docs/history/storage-build-20261020/tests/test_v2_native_config.py index eb4521d3..53fc244f 100644 --- a/tests/test_v2_native_config.py +++ b/docs/history/storage-build-20261020/tests/test_v2_native_config.py @@ -6,7 +6,8 @@ import unittest from unittest.mock import MagicMock -BUILD = Path(__file__).resolve().parents[1] / 'build/v2' +# Archived with the historical V2 launcher it exercises. +BUILD = Path(__file__).resolve().parents[1] / 'v2' spec = importlib.util.spec_from_file_location('native_config', BUILD / 'native_config.py') native = importlib.util.module_from_spec(spec) spec.loader.exec_module(native) diff --git a/tests/test_v2_web_paths.py b/docs/history/storage-build-20261020/tests/test_v2_web_paths.py similarity index 97% rename from tests/test_v2_web_paths.py rename to docs/history/storage-build-20261020/tests/test_v2_web_paths.py index a2abf2c3..29f5e27b 100644 --- a/tests/test_v2_web_paths.py +++ b/docs/history/storage-build-20261020/tests/test_v2_web_paths.py @@ -13,7 +13,8 @@ from urllib.request import Request, urlopen -BUILD = Path(__file__).resolve().parents[1] / 'build/v2' +# Archived with the historical V2 launcher it exercises. +BUILD = Path(__file__).resolve().parents[1] / 'v2' spec = importlib.util.spec_from_file_location('web_paths', BUILD / 'web_paths.py') paths = importlib.util.module_from_spec(spec) spec.loader.exec_module(paths) @@ -103,8 +104,8 @@ def test_invalid_origin_and_canonical_cannot_enter_nginx(self): def test_fileserver_proxies_overwrite_client_real_ip(self): # The Go fileserver records X-Real-IP from loopback as the Checkout IP, # so every nginx location in front of it must replace the client value. - root = BUILD.parents[1] - for name in ('build/v2/nginx-dev.conf', 'services/seafile.nginx.conf', + root = Path(__file__).resolve().parents[4] + for name in ('docs/history/storage-build-20261020/v2/nginx-dev.conf', 'services/seafile.nginx.conf', 'templates/seafile.nginx.conf.template'): for block in (root / name).read_text().split('location')[1:]: if 'proxy_pass http://127.0.0.1:8082' in block: diff --git a/scripts/scripts_14.0/bootstrap.py b/scripts/scripts_14.0/bootstrap.py index 8ab03d79..26cd076f 100755 --- a/scripts/scripts_14.0/bootstrap.py +++ b/scripts/scripts_14.0/bootstrap.py @@ -838,7 +838,8 @@ def write_cloudfile_seafile_conf(): dir_acl_enabled -- so a new capability needs no change here. """ lines = _seafile_conf_cloudfile_lines() - from library_storage import auto_storage_enabled, configure_auto_storage, retained_legacy_storage + from library_storage import (auto_storage_enabled, configure_auto_storage, + retained_legacy_storage, retained_legacy_switch) conf_path = join(topdir, 'conf', 'seafile.conf') fresh = globals().get('_cloudfile_fresh_install', False) if auto_storage_enabled(conf_path, os.environ, fresh=fresh): @@ -921,7 +922,13 @@ def write_cloudfile_seafile_conf(): if (not cf_enabled('CF_ENABLE_S3_STORAGE') or not get_conf('SEAF_SERVER_STORAGE_TYPE', '').strip()): - lines.append(retained_legacy_storage(conf_path)) + retained = retained_legacy_storage(conf_path) + # Retained multi routing keeps its allocator switch; otherwise new + # libraries would silently move from automatic directories to default. + if retained and retained_legacy_switch(conf_path): + lines = ['s3_storage_enabled = true\n' if line == 's3_storage_enabled = false\n' else line + for line in lines] + lines.append(retained) _replace_block(join(topdir, 'conf', 'seafile.conf'), CF_BEGIN, CF_END, ''.join(lines)) diff --git a/scripts/scripts_14.0/library_storage.py b/scripts/scripts_14.0/library_storage.py index b4cc5d5f..e0a8e782 100644 --- a/scripts/scripts_14.0/library_storage.py +++ b/scripts/scripts_14.0/library_storage.py @@ -58,6 +58,21 @@ def retained_legacy_storage(path): return output.getvalue() +def retained_legacy_switch(path): + """Keep the legacy allocator switch together with retained storage classes. + + The removed Compose overlay also supplied CF_ENABLE_S3_STORAGE=true. Its + persisted multi routing survives without that value, so new libraries must + keep the same automatic allocation rather than silently using the default. + """ + path = Path(path) + if not path.exists(): + return False + current = _read_config(path) + return (current.getboolean('storage', 'enable_storage_classes', fallback=False) and + current.getboolean('cloudfile', 's3_storage_enabled', fallback=False)) + + def _require_root(): root = Path(LIBRARY_ROOT) for path in (root, *root.parents): diff --git a/tests/test_application_assembly.py b/tests/test_application_assembly.py index 27fe5557..cd473a4f 100644 --- a/tests/test_application_assembly.py +++ b/tests/test_application_assembly.py @@ -80,7 +80,7 @@ def test_new_hub_preserves_native_bytes_admin_bundle_and_base(self): self.assertTrue((self.package / 'seahub/media/assets/frontend/static/js/old.js').exists()) def test_changed_native_pin_refuses_reuse_without_creating_output(self): - self.manifest['sources']['seafile-server']['ref'] = 'a' * 40 + self.manifest['sources']['seafile-server']['ref'] = 'c' * 40 self.manifest_path.write_text(json.dumps(self.manifest)) with self.assertRaisesRegex(ValueError, 'non-Hub'): self.run_assembly() diff --git a/tests/test_library_storage_config.py b/tests/test_library_storage_config.py index 496ca18d..bd2c65b3 100644 --- a/tests/test_library_storage_config.py +++ b/tests/test_library_storage_config.py @@ -68,7 +68,7 @@ def configure(self, cli=None): storage.configure_auto_storage(self.conf, '[cloudfile]\nexample = true\n', self.installdir, self.env) return output.getvalue() - def bootstrap_writer(self, environment): + def bootstrap_writer(self, environment, lines=('[cloudfile]\nexample = true\n',)): source = SOURCE.with_name('bootstrap.py') names = {'write_cloudfile_seafile_conf', '_replace_block'} nodes = [node for node in ast.parse(source.read_text()).body @@ -79,7 +79,7 @@ def bootstrap_writer(self, environment): get_conf=lambda key, default='': environment.get(key, default), cf_enabled=lambda key: environment.get(key, 'false') == 'true', logwarning=lambda message: None, - _seafile_conf_cloudfile_lines=lambda: ['[cloudfile]\nexample = true\n']) + _seafile_conf_cloudfile_lines=lambda: list(lines)) exec(compile(ast.Module(body=nodes, type_ignores=[]), str(source), 'exec'), namespace) return namespace['write_cloudfile_seafile_conf'] @@ -112,6 +112,29 @@ def test_bootstrap_legacy_multi_survives_environment_overlay_removal(self): self.assertEqual('multiple', config['block_backend']['name']) self.assertFalse(config.has_section('cloudfile_v2')) + def test_bootstrap_legacy_multi_keeps_automatic_allocator_switch(self): + self.conf.write_text(self.original + storage.BEGIN + '\n[cloudfile]\ns3_storage_enabled = true\n' + + '[storage]\nenable_storage_classes = true\nstorage_classes_file = existing.json\n' + + '[block_backend]\nname = multiple\n' + storage.END + '\n') + generated = ('[cloudfile]\n', 's3_storage_enabled = false\n') + with patch.dict(sys.modules, {'library_storage': storage}), patch.dict(os.environ, {}, clear=True): + self.bootstrap_writer({'CF_ENABLE_S3_STORAGE': 'false'}, generated)() + config = ConfigParser(interpolation=None) + config.read(self.conf) + self.assertEqual('true', config['cloudfile']['s3_storage_enabled']) + self.assertEqual('multiple', config['block_backend']['name']) + + def test_bootstrap_without_retained_multi_keeps_switch_disabled(self): + self.conf.write_text(self.original + storage.BEGIN + '\n[cloudfile]\ns3_storage_enabled = true\n' + + storage.END + '\n') + generated = ('[cloudfile]\n', 's3_storage_enabled = false\n') + with patch.dict(sys.modules, {'library_storage': storage}), patch.dict(os.environ, {}, clear=True): + self.bootstrap_writer({'CF_ENABLE_S3_STORAGE': 'false'}, generated)() + config = ConfigParser(interpolation=None) + config.read(self.conf) + self.assertEqual('false', config['cloudfile']['s3_storage_enabled']) + self.assertFalse(config.has_section('storage')) + def test_bootstrap_s3_switch_without_removed_overlay_type_keeps_multi(self): original = self.original + storage.BEGIN + '\n[cloudfile]\nexample = true\n' + ( '[storage]\nenable_storage_classes = true\nstorage_classes_file = existing.json\n' +