fix: Add `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` for read-only filesystem support (#20492)
Fixes #19578
---
When deploying the LiteLLM proxy with `readOnlyRootFilesystem: true` in Kubernetes, UI routes returned `404` because:
- Hardcoded paths:
- `/var/lib/litellm/ui`
- `/var/lib/litellm/assets`
- Runtime copy/restructure operations failed on read-only filesystems
- No detection mechanism for pre-restructured UI
---
Add configurable environment variables with intelligent detection, graceful fallbacks, and code quality improvements.
---
- **`LITELLM_UI_PATH`** — Custom UI directory location
- Default: `/var/lib/litellm/ui` (when `LITELLM_NON_ROOT=true`)
- Default: packaged UI path (otherwise)
- Example: `/app/var/litellm/ui` for `emptyDir` volumes
- **`LITELLM_ASSETS_PATH`** — Custom assets directory location
- Default: `/var/lib/litellm/assets` (when `LITELLM_NON_ROOT=true`)
- Default: current working directory (otherwise)
- Example: `/app/var/litellm/assets`
---
UI is detected as **pre-restructured and ready** if any of the following apply:
1. **Primary**: `.litellm_ui_ready` marker file exists (created by Dockerfile)
2. **Fallback**: Pattern-based detection — finds *any* subdirectory containing `index.html`
(resilient to UI structure changes; no hardcoded route names)
3. **Safety**: Filesystem writability check before operations
---
**`litellm/proxy/proxy_server.py`**
- `_validate_ui_directory()` — Verifies UI has required structure (`index.html`, `_next/`)
- `_is_ui_pre_restructured()` — Pattern-based detection (not hardcoded routes)
- `_try_populate_ui_directory()` — Helper for clean error handling
- Refactored UI path decision tree with numbered cases (1, 2, 3, 4a, 4b)
- Updated UI path logic to use `LITELLM_UI_PATH`
- Added writability checks before copy/restructure operations
- Graceful fallback to packaged UI if operations fail
- Updated `server_root_path` replacement with read-only check
- Simplified assets directory creation (try/except instead of complex parent checks)
- Updated `get_image()` endpoint to use `LITELLM_ASSETS_PATH`
- Added validation for packaged and final UI paths
**`docker/Dockerfile.non_root`**
- Added `touch .litellm_ui_ready` marker after UI restructuring
- Enables automatic detection of pre-built UI in Docker images
**`tests/proxy_unit_tests/test_ui_path_detection.py`**
- Added comprehensive unit tests for new functionality
- Tests env var handling, detection logic, and writability checks
---
**`docs/my-website/docs/proxy/config_settings.md`**
- Added `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` to env vars table
- Documented defaults and use cases
**`docs/my-website/docs/proxy/prod.md`**
- Added comprehensive "Read-Only Root Filesystem" section
- Quick fixes for permission errors
- Full Kubernetes setup with `initContainer` + `emptyDir` volumes
- API-only deployment option
- Environment variables reference table
- Notes on migrations, caching, and `server_root_path`
**`docker/README.md`**
- Updated hardened setup notes to mention pre-built UI
- Added details about UI serving from read-only paths
---
- No breaking changes
- Existing deployments continue working without modifications
- New env vars are optional with sensible defaults
- Detection logic supports both old and new builds
- Graceful fallbacks throughout
---
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
- name: setup-ui
image: ghcr.io/berriai/litellm:main-stable
command: ["sh", "-c", "cp -r /var/lib/litellm/ui/* /app/var/litellm/ui/"]
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
containers:
- name: litellm
env:
- name: LITELLM_UI_PATH
value: "/app/var/litellm/ui"
- name: LITELLM_ASSETS_PATH
value: "/app/var/litellm/assets"
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
volumes:
- name: ui-volume
emptyDir:
sizeLimit: 100Mi
2026-02-06 22:59:36 +08:00
|
|
|
"""
|
|
|
|
|
Unit tests for UI path detection and configuration.
|
|
|
|
|
|
|
|
|
|
Tests the new LITELLM_UI_PATH and LITELLM_ASSETS_PATH functionality
|
|
|
|
|
for read-only filesystem support.
|
|
|
|
|
|
|
|
|
|
Note: Tests involving proxy_server imports are intentionally minimal
|
|
|
|
|
to avoid long module load times during testing.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest import mock
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUIPathEnvironmentVariable:
|
|
|
|
|
"""Test LITELLM_UI_PATH environment variable handling."""
|
|
|
|
|
|
|
|
|
|
def test_custom_ui_path_env_var(self):
|
|
|
|
|
"""Test that LITELLM_UI_PATH overrides default."""
|
|
|
|
|
custom_path = "/custom/ui/path"
|
|
|
|
|
|
|
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ, {"LITELLM_UI_PATH": custom_path, "LITELLM_NON_ROOT": "true"}
|
|
|
|
|
):
|
|
|
|
|
is_non_root = os.getenv("LITELLM_NON_ROOT", "").lower() == "true"
|
|
|
|
|
default_runtime_ui_path = (
|
|
|
|
|
"/var/lib/litellm/ui" if is_non_root else "/default/packaged/path"
|
|
|
|
|
)
|
|
|
|
|
runtime_ui_path = os.getenv("LITELLM_UI_PATH", default_runtime_ui_path)
|
|
|
|
|
|
|
|
|
|
assert runtime_ui_path == custom_path
|
|
|
|
|
|
|
|
|
|
def test_default_ui_path_non_root(self):
|
|
|
|
|
"""Test default UI path in non-root mode."""
|
2026-04-18 04:02:59 +08:00
|
|
|
with mock.patch.dict(os.environ, {"LITELLM_NON_ROOT": "true"}, clear=False):
|
fix: Add `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` for read-only filesystem support (#20492)
Fixes #19578
---
When deploying the LiteLLM proxy with `readOnlyRootFilesystem: true` in Kubernetes, UI routes returned `404` because:
- Hardcoded paths:
- `/var/lib/litellm/ui`
- `/var/lib/litellm/assets`
- Runtime copy/restructure operations failed on read-only filesystems
- No detection mechanism for pre-restructured UI
---
Add configurable environment variables with intelligent detection, graceful fallbacks, and code quality improvements.
---
- **`LITELLM_UI_PATH`** — Custom UI directory location
- Default: `/var/lib/litellm/ui` (when `LITELLM_NON_ROOT=true`)
- Default: packaged UI path (otherwise)
- Example: `/app/var/litellm/ui` for `emptyDir` volumes
- **`LITELLM_ASSETS_PATH`** — Custom assets directory location
- Default: `/var/lib/litellm/assets` (when `LITELLM_NON_ROOT=true`)
- Default: current working directory (otherwise)
- Example: `/app/var/litellm/assets`
---
UI is detected as **pre-restructured and ready** if any of the following apply:
1. **Primary**: `.litellm_ui_ready` marker file exists (created by Dockerfile)
2. **Fallback**: Pattern-based detection — finds *any* subdirectory containing `index.html`
(resilient to UI structure changes; no hardcoded route names)
3. **Safety**: Filesystem writability check before operations
---
**`litellm/proxy/proxy_server.py`**
- `_validate_ui_directory()` — Verifies UI has required structure (`index.html`, `_next/`)
- `_is_ui_pre_restructured()` — Pattern-based detection (not hardcoded routes)
- `_try_populate_ui_directory()` — Helper for clean error handling
- Refactored UI path decision tree with numbered cases (1, 2, 3, 4a, 4b)
- Updated UI path logic to use `LITELLM_UI_PATH`
- Added writability checks before copy/restructure operations
- Graceful fallback to packaged UI if operations fail
- Updated `server_root_path` replacement with read-only check
- Simplified assets directory creation (try/except instead of complex parent checks)
- Updated `get_image()` endpoint to use `LITELLM_ASSETS_PATH`
- Added validation for packaged and final UI paths
**`docker/Dockerfile.non_root`**
- Added `touch .litellm_ui_ready` marker after UI restructuring
- Enables automatic detection of pre-built UI in Docker images
**`tests/proxy_unit_tests/test_ui_path_detection.py`**
- Added comprehensive unit tests for new functionality
- Tests env var handling, detection logic, and writability checks
---
**`docs/my-website/docs/proxy/config_settings.md`**
- Added `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` to env vars table
- Documented defaults and use cases
**`docs/my-website/docs/proxy/prod.md`**
- Added comprehensive "Read-Only Root Filesystem" section
- Quick fixes for permission errors
- Full Kubernetes setup with `initContainer` + `emptyDir` volumes
- API-only deployment option
- Environment variables reference table
- Notes on migrations, caching, and `server_root_path`
**`docker/README.md`**
- Updated hardened setup notes to mention pre-built UI
- Added details about UI serving from read-only paths
---
- No breaking changes
- Existing deployments continue working without modifications
- New env vars are optional with sensible defaults
- Detection logic supports both old and new builds
- Graceful fallbacks throughout
---
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
- name: setup-ui
image: ghcr.io/berriai/litellm:main-stable
command: ["sh", "-c", "cp -r /var/lib/litellm/ui/* /app/var/litellm/ui/"]
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
containers:
- name: litellm
env:
- name: LITELLM_UI_PATH
value: "/app/var/litellm/ui"
- name: LITELLM_ASSETS_PATH
value: "/app/var/litellm/assets"
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
volumes:
- name: ui-volume
emptyDir:
sizeLimit: 100Mi
2026-02-06 22:59:36 +08:00
|
|
|
# Clear LITELLM_UI_PATH if it exists
|
|
|
|
|
env_copy = os.environ.copy()
|
|
|
|
|
if "LITELLM_UI_PATH" in env_copy:
|
|
|
|
|
del env_copy["LITELLM_UI_PATH"]
|
|
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, env_copy, clear=True):
|
|
|
|
|
is_non_root = os.getenv("LITELLM_NON_ROOT", "").lower() == "true"
|
|
|
|
|
default_runtime_ui_path = (
|
2026-04-18 04:02:59 +08:00
|
|
|
"/var/lib/litellm/ui" if is_non_root else "/default/packaged/path"
|
fix: Add `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` for read-only filesystem support (#20492)
Fixes #19578
---
When deploying the LiteLLM proxy with `readOnlyRootFilesystem: true` in Kubernetes, UI routes returned `404` because:
- Hardcoded paths:
- `/var/lib/litellm/ui`
- `/var/lib/litellm/assets`
- Runtime copy/restructure operations failed on read-only filesystems
- No detection mechanism for pre-restructured UI
---
Add configurable environment variables with intelligent detection, graceful fallbacks, and code quality improvements.
---
- **`LITELLM_UI_PATH`** — Custom UI directory location
- Default: `/var/lib/litellm/ui` (when `LITELLM_NON_ROOT=true`)
- Default: packaged UI path (otherwise)
- Example: `/app/var/litellm/ui` for `emptyDir` volumes
- **`LITELLM_ASSETS_PATH`** — Custom assets directory location
- Default: `/var/lib/litellm/assets` (when `LITELLM_NON_ROOT=true`)
- Default: current working directory (otherwise)
- Example: `/app/var/litellm/assets`
---
UI is detected as **pre-restructured and ready** if any of the following apply:
1. **Primary**: `.litellm_ui_ready` marker file exists (created by Dockerfile)
2. **Fallback**: Pattern-based detection — finds *any* subdirectory containing `index.html`
(resilient to UI structure changes; no hardcoded route names)
3. **Safety**: Filesystem writability check before operations
---
**`litellm/proxy/proxy_server.py`**
- `_validate_ui_directory()` — Verifies UI has required structure (`index.html`, `_next/`)
- `_is_ui_pre_restructured()` — Pattern-based detection (not hardcoded routes)
- `_try_populate_ui_directory()` — Helper for clean error handling
- Refactored UI path decision tree with numbered cases (1, 2, 3, 4a, 4b)
- Updated UI path logic to use `LITELLM_UI_PATH`
- Added writability checks before copy/restructure operations
- Graceful fallback to packaged UI if operations fail
- Updated `server_root_path` replacement with read-only check
- Simplified assets directory creation (try/except instead of complex parent checks)
- Updated `get_image()` endpoint to use `LITELLM_ASSETS_PATH`
- Added validation for packaged and final UI paths
**`docker/Dockerfile.non_root`**
- Added `touch .litellm_ui_ready` marker after UI restructuring
- Enables automatic detection of pre-built UI in Docker images
**`tests/proxy_unit_tests/test_ui_path_detection.py`**
- Added comprehensive unit tests for new functionality
- Tests env var handling, detection logic, and writability checks
---
**`docs/my-website/docs/proxy/config_settings.md`**
- Added `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` to env vars table
- Documented defaults and use cases
**`docs/my-website/docs/proxy/prod.md`**
- Added comprehensive "Read-Only Root Filesystem" section
- Quick fixes for permission errors
- Full Kubernetes setup with `initContainer` + `emptyDir` volumes
- API-only deployment option
- Environment variables reference table
- Notes on migrations, caching, and `server_root_path`
**`docker/README.md`**
- Updated hardened setup notes to mention pre-built UI
- Added details about UI serving from read-only paths
---
- No breaking changes
- Existing deployments continue working without modifications
- New env vars are optional with sensible defaults
- Detection logic supports both old and new builds
- Graceful fallbacks throughout
---
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
- name: setup-ui
image: ghcr.io/berriai/litellm:main-stable
command: ["sh", "-c", "cp -r /var/lib/litellm/ui/* /app/var/litellm/ui/"]
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
containers:
- name: litellm
env:
- name: LITELLM_UI_PATH
value: "/app/var/litellm/ui"
- name: LITELLM_ASSETS_PATH
value: "/app/var/litellm/assets"
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
volumes:
- name: ui-volume
emptyDir:
sizeLimit: 100Mi
2026-02-06 22:59:36 +08:00
|
|
|
)
|
2026-04-18 04:02:59 +08:00
|
|
|
runtime_ui_path = os.getenv("LITELLM_UI_PATH", default_runtime_ui_path)
|
fix: Add `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` for read-only filesystem support (#20492)
Fixes #19578
---
When deploying the LiteLLM proxy with `readOnlyRootFilesystem: true` in Kubernetes, UI routes returned `404` because:
- Hardcoded paths:
- `/var/lib/litellm/ui`
- `/var/lib/litellm/assets`
- Runtime copy/restructure operations failed on read-only filesystems
- No detection mechanism for pre-restructured UI
---
Add configurable environment variables with intelligent detection, graceful fallbacks, and code quality improvements.
---
- **`LITELLM_UI_PATH`** — Custom UI directory location
- Default: `/var/lib/litellm/ui` (when `LITELLM_NON_ROOT=true`)
- Default: packaged UI path (otherwise)
- Example: `/app/var/litellm/ui` for `emptyDir` volumes
- **`LITELLM_ASSETS_PATH`** — Custom assets directory location
- Default: `/var/lib/litellm/assets` (when `LITELLM_NON_ROOT=true`)
- Default: current working directory (otherwise)
- Example: `/app/var/litellm/assets`
---
UI is detected as **pre-restructured and ready** if any of the following apply:
1. **Primary**: `.litellm_ui_ready` marker file exists (created by Dockerfile)
2. **Fallback**: Pattern-based detection — finds *any* subdirectory containing `index.html`
(resilient to UI structure changes; no hardcoded route names)
3. **Safety**: Filesystem writability check before operations
---
**`litellm/proxy/proxy_server.py`**
- `_validate_ui_directory()` — Verifies UI has required structure (`index.html`, `_next/`)
- `_is_ui_pre_restructured()` — Pattern-based detection (not hardcoded routes)
- `_try_populate_ui_directory()` — Helper for clean error handling
- Refactored UI path decision tree with numbered cases (1, 2, 3, 4a, 4b)
- Updated UI path logic to use `LITELLM_UI_PATH`
- Added writability checks before copy/restructure operations
- Graceful fallback to packaged UI if operations fail
- Updated `server_root_path` replacement with read-only check
- Simplified assets directory creation (try/except instead of complex parent checks)
- Updated `get_image()` endpoint to use `LITELLM_ASSETS_PATH`
- Added validation for packaged and final UI paths
**`docker/Dockerfile.non_root`**
- Added `touch .litellm_ui_ready` marker after UI restructuring
- Enables automatic detection of pre-built UI in Docker images
**`tests/proxy_unit_tests/test_ui_path_detection.py`**
- Added comprehensive unit tests for new functionality
- Tests env var handling, detection logic, and writability checks
---
**`docs/my-website/docs/proxy/config_settings.md`**
- Added `LITELLM_UI_PATH` and `LITELLM_ASSETS_PATH` to env vars table
- Documented defaults and use cases
**`docs/my-website/docs/proxy/prod.md`**
- Added comprehensive "Read-Only Root Filesystem" section
- Quick fixes for permission errors
- Full Kubernetes setup with `initContainer` + `emptyDir` volumes
- API-only deployment option
- Environment variables reference table
- Notes on migrations, caching, and `server_root_path`
**`docker/README.md`**
- Updated hardened setup notes to mention pre-built UI
- Added details about UI serving from read-only paths
---
- No breaking changes
- Existing deployments continue working without modifications
- New env vars are optional with sensible defaults
- Detection logic supports both old and new builds
- Graceful fallbacks throughout
---
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
initContainers:
- name: setup-ui
image: ghcr.io/berriai/litellm:main-stable
command: ["sh", "-c", "cp -r /var/lib/litellm/ui/* /app/var/litellm/ui/"]
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
containers:
- name: litellm
env:
- name: LITELLM_UI_PATH
value: "/app/var/litellm/ui"
- name: LITELLM_ASSETS_PATH
value: "/app/var/litellm/assets"
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: ui-volume
mountPath: /app/var/litellm/ui
volumes:
- name: ui-volume
emptyDir:
sizeLimit: 100Mi
2026-02-06 22:59:36 +08:00
|
|
|
|
|
|
|
|
assert runtime_ui_path == "/var/lib/litellm/ui"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestAssetsPathEnvironmentVariable:
|
|
|
|
|
"""Test LITELLM_ASSETS_PATH environment variable handling."""
|
|
|
|
|
|
|
|
|
|
def test_custom_assets_path_env_var(self):
|
|
|
|
|
"""Test that LITELLM_ASSETS_PATH overrides default."""
|
|
|
|
|
custom_path = "/custom/assets/path"
|
|
|
|
|
|
|
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{"LITELLM_ASSETS_PATH": custom_path, "LITELLM_NON_ROOT": "true"},
|
|
|
|
|
):
|
|
|
|
|
is_non_root = os.getenv("LITELLM_NON_ROOT", "").lower() == "true"
|
|
|
|
|
default_assets_dir = (
|
|
|
|
|
"/var/lib/litellm/assets" if is_non_root else "/default/current/dir"
|
|
|
|
|
)
|
|
|
|
|
assets_dir = os.getenv("LITELLM_ASSETS_PATH", default_assets_dir)
|
|
|
|
|
|
|
|
|
|
assert assets_dir == custom_path
|
|
|
|
|
|
|
|
|
|
def test_default_assets_path_non_root(self):
|
|
|
|
|
"""Test default assets path in non-root mode."""
|
|
|
|
|
env_copy = os.environ.copy()
|
|
|
|
|
env_copy["LITELLM_NON_ROOT"] = "true"
|
|
|
|
|
if "LITELLM_ASSETS_PATH" in env_copy:
|
|
|
|
|
del env_copy["LITELLM_ASSETS_PATH"]
|
|
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, env_copy, clear=True):
|
|
|
|
|
is_non_root = os.getenv("LITELLM_NON_ROOT", "").lower() == "true"
|
|
|
|
|
default_assets_dir = (
|
|
|
|
|
"/var/lib/litellm/assets" if is_non_root else "/default/current/dir"
|
|
|
|
|
)
|
|
|
|
|
assets_dir = os.getenv("LITELLM_ASSETS_PATH", default_assets_dir)
|
|
|
|
|
|
|
|
|
|
assert assets_dir == "/var/lib/litellm/assets"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUIDetectionLogic:
|
|
|
|
|
"""Test UI pre-restructured detection logic without importing proxy_server."""
|
|
|
|
|
|
|
|
|
|
def setup_method(self):
|
|
|
|
|
"""Create temporary directory for testing."""
|
|
|
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
|
|
|
|
|
|
|
|
def teardown_method(self):
|
|
|
|
|
"""Clean up temporary directory."""
|
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
|
|
if os.path.exists(self.temp_dir):
|
|
|
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
|
|
|
|
|
|
def test_marker_file_exists(self):
|
|
|
|
|
"""Test marker file detection logic."""
|
|
|
|
|
marker_path = os.path.join(self.temp_dir, ".litellm_ui_ready")
|
|
|
|
|
Path(marker_path).touch()
|
|
|
|
|
|
|
|
|
|
# Verify marker file exists
|
|
|
|
|
assert os.path.exists(marker_path)
|
|
|
|
|
|
|
|
|
|
def test_structural_routes_exist(self):
|
|
|
|
|
"""Test structural detection logic."""
|
|
|
|
|
routes = ["login", "guardrails", "logs"]
|
|
|
|
|
for route in routes:
|
|
|
|
|
route_dir = os.path.join(self.temp_dir, route)
|
|
|
|
|
os.makedirs(route_dir, exist_ok=True)
|
|
|
|
|
index_html = os.path.join(route_dir, "index.html")
|
|
|
|
|
Path(index_html).touch()
|
|
|
|
|
|
|
|
|
|
# Verify routes exist
|
|
|
|
|
found_routes = 0
|
|
|
|
|
expected_routes = ["login", "guardrails", "logs", "api-reference"]
|
|
|
|
|
for route in expected_routes:
|
|
|
|
|
route_index = os.path.join(self.temp_dir, route, "index.html")
|
|
|
|
|
if os.path.exists(route_index):
|
|
|
|
|
found_routes += 1
|
|
|
|
|
|
|
|
|
|
assert found_routes >= 3
|
|
|
|
|
|
|
|
|
|
def test_writability_check(self):
|
|
|
|
|
"""Test that os.access() correctly detects writable directories."""
|
|
|
|
|
# Should be writable
|
|
|
|
|
assert os.access(self.temp_dir, os.W_OK) is True
|
|
|
|
|
|
|
|
|
|
# Create a directory we can't write to (platform-dependent)
|
|
|
|
|
if os.name != "nt": # Skip on Windows
|
|
|
|
|
readonly_dir = os.path.join(self.temp_dir, "readonly")
|
|
|
|
|
os.makedirs(readonly_dir)
|
|
|
|
|
os.chmod(readonly_dir, 0o444) # Read-only
|
|
|
|
|
|
|
|
|
|
# Should not be writable
|
|
|
|
|
assert os.access(readonly_dir, os.W_OK) is False
|
|
|
|
|
|
|
|
|
|
# Restore permissions for cleanup
|
|
|
|
|
os.chmod(readonly_dir, 0o755)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
pytest.main([__file__, "-v"])
|