diff --git a/Docker/release.sh b/Docker/release.sh index 1876cce..8ca2d23 100644 --- a/Docker/release.sh +++ b/Docker/release.sh @@ -68,6 +68,15 @@ FRONT_PKG="${SCRIPT_DIR}/../frontend/package.json" sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${FRONT_VERSION}\"/" "${FRONT_PKG}" echo "frontend/package.json version updated → ${FRONT_VERSION}" +# Keep backend/pyproject.toml in sync so app.__version__ matches Docker/VERSION in the runtime container. +BACKEND_PYPROJECT="${SCRIPT_DIR}/../backend/pyproject.toml" +if [[ -f "${BACKEND_PYPROJECT}" ]]; then + sed -i "s/^version = \".*\"/version = \"${FRONT_VERSION}\"/" "${BACKEND_PYPROJECT}" + echo "backend/pyproject.toml version updated → ${FRONT_VERSION}" +else + echo "Warning: backend/pyproject.toml not found, skipping backend version sync" >&2 +fi + # --------------------------------------------------------------------------- # Push containers # --------------------------------------------------------------------------- diff --git a/backend/EXTRACTION_SUMMARY.md b/backend/EXTRACTION_SUMMARY.md deleted file mode 100644 index 04005d8..0000000 --- a/backend/EXTRACTION_SUMMARY.md +++ /dev/null @@ -1,224 +0,0 @@ -# Config File Service Extraction Summary - -## ✓ Extraction Complete - -Three new service modules have been created by extracting functions from `config_file_service.py`. - -### Files Created - -| File | Lines | Status | -|------|-------|--------| -| [jail_config_service.py](jail_config_service.py) | 991 | ✓ Created | -| [filter_config_service.py](filter_config_service.py) | 765 | ✓ Created | -| [action_config_service.py](action_config_service.py) | 988 | ✓ Created | -| **Total** | **2,744** | **✓ Verified** | - ---- - -## 1. JAIL_CONFIG Service (`jail_config_service.py`) - -### Public Functions (7) -- `list_inactive_jails(config_dir, socket_path)` → InactiveJailListResponse -- `activate_jail(config_dir, socket_path, name, req)` → JailActivationResponse -- `deactivate_jail(config_dir, socket_path, name)` → JailActivationResponse -- `delete_jail_local_override(config_dir, socket_path, name)` → None -- `validate_jail_config(config_dir, name)` → JailValidationResult -- `rollback_jail(config_dir, socket_path, name, start_cmd_parts)` → RollbackResponse -- `_rollback_activation_async(config_dir, name, socket_path, original_content)` → bool - -### Helper Functions (5) -- `_write_local_override_sync()` - Atomic write of jail.d/{name}.local -- `_restore_local_file_sync()` - Restore or delete .local file during rollback -- `_validate_regex_patterns()` - Validate failregex/ignoreregex patterns -- `_set_jail_local_key_sync()` - Update single key in jail section -- `_validate_jail_config_sync()` - Synchronous validation (filter/action files, patterns, logpath) - -### Custom Exceptions (3) -- `JailNotFoundInConfigError` -- `JailAlreadyActiveError` -- `JailAlreadyInactiveError` - -### Shared Dependencies Imported -- `_safe_jail_name()` - From config_file_service -- `_parse_jails_sync()` - From config_file_service -- `_build_inactive_jail()` - From config_file_service -- `_get_active_jail_names()` - From config_file_service -- `_probe_fail2ban_running()` - From config_file_service -- `wait_for_fail2ban()` - From config_file_service -- `start_daemon()` - From config_file_service -- `_resolve_filter()` - From config_file_service -- `_parse_multiline()` - From config_file_service -- `_SOCKET_TIMEOUT`, `_META_SECTIONS` - Constants - ---- - -## 2. FILTER_CONFIG Service (`filter_config_service.py`) - -### Public Functions (6) -- `list_filters(config_dir, socket_path)` → FilterListResponse -- `get_filter(config_dir, socket_path, name)` → FilterConfig -- `update_filter(config_dir, socket_path, name, req, do_reload=False)` → FilterConfig -- `create_filter(config_dir, socket_path, req, do_reload=False)` → FilterConfig -- `delete_filter(config_dir, name)` → None -- `assign_filter_to_jail(config_dir, socket_path, jail_name, req, do_reload=False)` → None - -### Helper Functions (4) -- `_extract_filter_base_name(filter_raw)` - Extract base name from filter string -- `_build_filter_to_jails_map()` - Map filters to jails using them -- `_parse_filters_sync()` - Scan filter.d/ and return tuples -- `_write_filter_local_sync()` - Atomic write of filter.d/{name}.local -- `_validate_regex_patterns()` - Validate regex patterns (shared with jail_config) - -### Custom Exceptions (5) -- `FilterNotFoundError` -- `FilterAlreadyExistsError` -- `FilterReadonlyError` -- `FilterInvalidRegexError` -- `FilterNameError` (re-exported from config_file_service) - -### Shared Dependencies Imported -- `_safe_filter_name()` - From config_file_service -- `_safe_jail_name()` - From config_file_service -- `_parse_jails_sync()` - From config_file_service -- `_get_active_jail_names()` - From config_file_service -- `_resolve_filter()` - From config_file_service -- `_parse_multiline()` - From config_file_service -- `_SAFE_FILTER_NAME_RE` - Constant pattern - ---- - -## 3. ACTION_CONFIG Service (`action_config_service.py`) - -### Public Functions (7) -- `list_actions(config_dir, socket_path)` → ActionListResponse -- `get_action(config_dir, socket_path, name)` → ActionConfig -- `update_action(config_dir, socket_path, name, req, do_reload=False)` → ActionConfig -- `create_action(config_dir, socket_path, req, do_reload=False)` → ActionConfig -- `delete_action(config_dir, name)` → None -- `assign_action_to_jail(config_dir, socket_path, jail_name, req, do_reload=False)` → None -- `remove_action_from_jail(config_dir, socket_path, jail_name, action_name, do_reload=False)` → None - -### Helper Functions (5) -- `_safe_action_name(name)` - Validate action name -- `_extract_action_base_name()` - Extract base name from action string -- `_build_action_to_jails_map()` - Map actions to jails using them -- `_parse_actions_sync()` - Scan action.d/ and return tuples -- `_append_jail_action_sync()` - Append action to jail.d/{name}.local -- `_remove_jail_action_sync()` - Remove action from jail.d/{name}.local -- `_write_action_local_sync()` - Atomic write of action.d/{name}.local - -### Custom Exceptions (4) -- `ActionNotFoundError` -- `ActionAlreadyExistsError` -- `ActionReadonlyError` -- `ActionNameError` - -### Shared Dependencies Imported -- `_safe_jail_name()` - From config_file_service -- `_parse_jails_sync()` - From config_file_service -- `_get_active_jail_names()` - From config_file_service -- `_build_parser()` - From config_file_service -- `_SAFE_ACTION_NAME_RE` - Constant pattern - ---- - -## 4. SHARED Utilities (remain in `config_file_service.py`) - -### Utility Functions (14) -- `_safe_jail_name(name)` → str -- `_safe_filter_name(name)` → str -- `_ordered_config_files(config_dir)` → list[Path] -- `_build_parser()` → configparser.RawConfigParser -- `_is_truthy(value)` → bool -- `_parse_int_safe(value)` → int | None -- `_parse_time_to_seconds(value, default)` → int -- `_parse_multiline(raw)` → list[str] -- `_resolve_filter(raw_filter, jail_name, mode)` → str -- `_parse_jails_sync(config_dir)` → tuple -- `_build_inactive_jail(name, settings, source_file, config_dir=None)` → InactiveJail -- `_get_active_jail_names(socket_path)` → set[str] -- `_probe_fail2ban_running(socket_path)` → bool -- `wait_for_fail2ban(socket_path, max_wait_seconds, poll_interval)` → bool -- `start_daemon(start_cmd_parts)` → bool - -### Shared Exceptions (3) -- `JailNameError` -- `FilterNameError` -- `ConfigWriteError` - -### Constants (7) -- `_SOCKET_TIMEOUT` -- `_SAFE_JAIL_NAME_RE` -- `_META_SECTIONS` -- `_TRUE_VALUES` -- `_FALSE_VALUES` - ---- - -## Import Dependencies - -### jail_config_service imports: -```python -config_file_service: (shared utilities + private functions) -jail_service.reload_all() -Fail2BanConnectionError -``` - -### filter_config_service imports: -```python -config_file_service: (shared utilities + _set_jail_local_key_sync) -jail_service.reload_all() -conffile_parser: (parse/merge/serialize filter functions) -jail_config_service: (JailNotFoundInConfigError - lazy import) -``` - -### action_config_service imports: -```python -config_file_service: (shared utilities + _build_parser) -jail_service.reload_all() -conffile_parser: (parse/merge/serialize action functions) -jail_config_service: (JailNotFoundInConfigError - lazy import) -``` - ---- - -## Cross-Service Dependencies - -**Circular imports handled via lazy imports:** -- `filter_config_service` imports `JailNotFoundInConfigError` from `jail_config_service` inside function -- `action_config_service` imports `JailNotFoundInConfigError` from `jail_config_service` inside function - -**Shared functions re-used:** -- `_set_jail_local_key_sync()` exported from `jail_config_service`, used by `filter_config_service` -- `_append_jail_action_sync()` and `_remove_jail_action_sync()` internal to `action_config_service` - ---- - -## Verification Results - -✓ **Syntax Check:** All three files compile without errors -✓ **Import Verification:** All imports resolved correctly -✓ **Total Lines:** 2,744 lines across three new files -✓ **Function Coverage:** 100% of specified functions extracted -✓ **Type Hints:** Preserved throughout -✓ **Docstrings:** All preserved with full documentation -✓ **Comments:** All inline comments preserved - ---- - -## Next Steps (if needed) - -1. **Update router imports** - Point from config_file_service to specific service modules: - - `jail_config_service` for jail operations - - `filter_config_service` for filter operations - - `action_config_service` for action operations - -2. **Update config_file_service.py** - Remove all extracted functions (optional cleanup) - - Optionally keep it as a facade/aggregator - - Or reduce it to only the shared utilities module - -3. **Add __all__ exports** to each new module for cleaner public API - -4. **Update type hints** in models if needed for cross-service usage - -5. **Testing** - Run existing tests to ensure no regressions diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4649476..2e2ed20 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bangui-backend" -version = "0.9.8" +version = "0.9.14" description = "BanGUI backend — fail2ban web management interface" requires-python = ">=3.12" dependencies = [ diff --git a/backend/tests/test_version.py b/backend/tests/test_version.py index 3a53898..7ebf125 100644 --- a/backend/tests/test_version.py +++ b/backend/tests/test_version.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +import re import app @@ -13,3 +14,15 @@ def test_app_version_matches_docker_version() -> None: expected = version_file.read_text(encoding="utf-8").strip().lstrip("v") assert app.__version__ == expected + + +def test_backend_pyproject_version_matches_docker_version() -> None: + repo_root = Path(__file__).resolve().parents[2] + version_file = repo_root / "Docker" / "VERSION" + expected = version_file.read_text(encoding="utf-8").strip().lstrip("v") + + pyproject_file = repo_root / "backend" / "pyproject.toml" + text = pyproject_file.read_text(encoding="utf-8") + match = re.search(r"^version\s*=\s*\"([^\"]+)\"", text, re.MULTILINE) + assert match is not None, "backend/pyproject.toml must contain a version entry" + assert match.group(1) == expected