* docs: rename FONT_MANAGER_USAGE.md to FONT_MANAGER.md Renamed for clearer naming convention. Part of documentation consolidation effort. * docs: consolidate Plugin Store guides (2→1) Merged: - PLUGIN_STORE_USER_GUIDE.md - PLUGIN_STORE_QUICK_REFERENCE.md Into: PLUGIN_STORE_GUIDE.md - Unified writing style to professional technical - Added Quick Reference section at top for easy access - Removed duplicate content - Added cross-references to related documentation - Updated formatting to match style guidelines * docs: create user-focused Web Interface Guide Created WEB_INTERFACE_GUIDE.md consolidating: - V3_INTERFACE_README.md (technical details) - User-facing interface documentation - Focused on end-user tasks and navigation - Removed technical implementation details - Added common tasks section - Included troubleshooting - Professional technical writing style * docs: consolidate WiFi setup guides (4→1) Merged: - WIFI_SETUP.md - OPTIMAL_WIFI_AP_FAILOVER_SETUP.md - AP_MODE_MANUAL_ENABLE.md - WIFI_ETHERNET_AP_MODE_FIX.md (behavior documentation) Into: WIFI_NETWORK_SETUP.md - Comprehensive coverage of WiFi setup and configuration - Clear explanation of AP mode failover and grace period - Configuration scenarios and best practices - Troubleshooting section combining all sources - Professional technical writing style - Added quick reference table for behavior * docs: consolidate troubleshooting guides (4→1) Merged: - TROUBLESHOOTING_QUICK_START.md - WEB_INTERFACE_TROUBLESHOOTING.md - CAPTIVE_PORTAL_TROUBLESHOOTING.md - WEATHER_TROUBLESHOOTING.md Into: TROUBLESHOOTING.md - Organized by issue category (web, WiFi, plugins) - Comprehensive diagnostic commands reference - Quick diagnosis steps at the top - Service file template preserved - Complete diagnostic script included - Professional technical writing style * docs: create consolidated Advanced Features guide Merged: - VEGAS_SCROLL_MODE.md - ON_DEMAND_DISPLAY_QUICK_START.md - ON_DEMAND_DISPLAY_API.md - ON_DEMAND_CACHE_MANAGEMENT.md - BACKGROUND_SERVICE_README.md - PERMISSION_MANAGEMENT_GUIDE.md Into: ADVANCED_FEATURES.md - Comprehensive guide covering all advanced features - Vegas scroll mode with integration examples - On-demand display with API reference - Cache management troubleshooting - Background service documentation - Permission management patterns - Professional technical writing style * docs: create Getting Started guide for first-time users Created GETTING_STARTED.md: - Quick start guide (5 minutes) - Initial configuration walkthrough - Common first-time issues and solutions - Next steps and quick reference - User-friendly tone for beginners - Links to detailed documentation * docs: archive consolidated source files and ephemeral docs Archived files that have been consolidated: - Plugin Store guides (2 files → PLUGIN_STORE_GUIDE.md) - Web Interface guide (V3_INTERFACE_README.md → WEB_INTERFACE_GUIDE.md) - WiFi Setup guides (4 files → WIFI_NETWORK_SETUP.md) - Troubleshooting guides (4 files → TROUBLESHOOTING.md) - Advanced Features (6 files → ADVANCED_FEATURES.md) Archived ephemeral/debug documentation: - DEBUG_WEB_ISSUE.md - BROWSER_ERRORS_EXPLANATION.md - FORM_VALIDATION_FIXES.md - WEB_UI_RELIABILITY_IMPROVEMENTS.md - CAPTIVE_PORTAL_TESTING.md - NEXT_STEPS_COMMANDS.md - STATIC_IMAGE_MULTI_UPLOAD_PLAN.md - RECONNECT_AFTER_CAPTIVE_PORTAL_TESTING.md Archived implementation summaries: - PLUGIN_CONFIG_TABS_SUMMARY.md - PLUGIN_CONFIG_SYSTEM_VERIFICATION.md - PLUGIN_SCHEMA_AUDIT_SUMMARY.md - STARTUP_OPTIMIZATION_SUMMARY.md - PLUGIN_DISPATCH_IMPLEMENTATION.md - NESTED_SCHEMA_IMPLEMENTATION.md - AP_MODE_MANUAL_ENABLE_CHANGES.md - PLUGIN_CONFIG_SYSTEM_EXPLANATION.md Total archived: 27 files Preserves git history while cleaning up main docs directory * docs: rename API_REFERENCE.md to REST_API_REFERENCE.md Renamed for clarity - this is specifically the REST API reference for the web interface, not a general API reference. * docs: update README.md to reflect consolidated documentation structure Updated documentation index: - Reflects new consolidated guides (51 → 16-17 files) - Updated Quick Start sections with new file names - Added consolidation history (January 2026) - Updated file references (API_REFERENCE → REST_API_REFERENCE) - Documented archival of 33 files - Added benefits of consolidation - Updated statistics and highlights - Removed outdated references - Professional writing style maintained throughout * docs: add Vegas scroll mode system architecture documentation Added comprehensive internal architecture section for Vegas mode: - Component overview with diagram - VegasModeCoordinator responsibilities and main loop - StreamManager buffering strategy and content flow - PluginAdapter integration and fallback behavior - RenderPipeline 125 FPS rendering process - Component interaction flows - Thread safety patterns - Performance characteristics Covers: - How the four components work together - Initialization and render loop flows - Config update handling - Frame rate management and optimization - Memory usage and CPU characteristics --------- Co-authored-by: Chuck <chuck@example.com>
15 KiB
Permission Management Guide
Overview
LEDMatrix runs with a dual-user architecture: the main display service runs as root (for hardware access), while the web interface runs as a regular user. This guide explains how to properly manage file and directory permissions to ensure both services can access the files they need.
Table of Contents
- Why Permission Management Matters
- Permission Utilities
- When to Use Permission Utilities
- How to Use Permission Utilities
- Common Patterns and Examples
- Permission Standards
- Troubleshooting
Why Permission Management Matters
The Problem
Without proper permission management, you may encounter errors like:
PermissionError: [Errno 13] Permission deniedwhen saving config filesPermissionErrorwhen downloading team logos- Files created by the root service not accessible by the web user
- Files created by the web user not accessible by the root service
The Solution
The LEDMatrix codebase includes centralized permission utilities (src/common/permission_utils.py) that ensure files and directories are created with appropriate permissions for both users.
Permission Utilities
Available Functions
The permission utilities module provides the following functions:
Directory Management
ensure_directory_permissions(path: Path, mode: int = 0o775) -> None- Creates directory if it doesn't exist
- Sets permissions to the specified mode
- Default mode:
0o775(rwxrwxr-x) - group-writable
File Management
ensure_file_permissions(path: Path, mode: int = 0o644) -> None- Sets permissions on an existing file
- Default mode:
0o644(rw-r--r--) - world-readable
Mode Helpers
These functions return the appropriate permission mode for different file types:
-
get_config_file_mode(file_path: Path) -> int- Returns
0o640for secrets files,0o644for regular config files
- Returns
-
get_assets_file_mode() -> int- Returns
0o664(rw-rw-r--) for asset files (logos, images)
- Returns
-
get_assets_dir_mode() -> int- Returns
0o2775(rwxrwsr-x) for asset directories - Setgid bit enforces inherited group ownership for new files/directories
- Returns
-
get_config_dir_mode() -> int- Returns
0o2775(rwxrwsr-x) for config directories - Setgid bit enforces inherited group ownership for new files/directories
- Returns
-
get_plugin_file_mode() -> int- Returns
0o664(rw-rw-r--) for plugin files
- Returns
-
get_plugin_dir_mode() -> int- Returns
0o2775(rwxrwsr-x) for plugin directories - Setgid bit enforces inherited group ownership for new files/directories
- Returns
-
get_cache_dir_mode() -> int- Returns
0o2775(rwxrwsr-x) for cache directories - Setgid bit enforces inherited group ownership for new files/directories
- Returns
When to Use Permission Utilities
Always Use Permission Utilities When:
- Creating directories - Use
ensure_directory_permissions()instead ofos.makedirs()orPath.mkdir() - Saving files - Use
ensure_file_permissions()after writing files - Downloading assets - Set permissions after downloading logos, images, or other assets
- Creating config files - Set permissions after saving configuration files
- Creating cache files - Set permissions when creating cache directories or files
- Plugin file operations - Set permissions when plugins create their own files/directories
You Don't Need Permission Utilities When:
- Reading files - Reading doesn't require permission changes
- Using core utilities - Core utilities (LogoHelper, CacheManager, ConfigManager) already handle permissions
- Temporary files - Files in
/tmpor created withtempfiledon't need special permissions
How to Use Permission Utilities
Basic Import
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_assets_dir_mode,
get_assets_file_mode,
get_config_dir_mode,
get_config_file_mode
)
Creating a Directory
Before (incorrect):
import os
os.makedirs("assets/sports/logos", exist_ok=True)
# Problem: Permissions may not be set correctly
After (correct):
from pathlib import Path
from src.common.permission_utils import ensure_directory_permissions, get_assets_dir_mode
logo_dir = Path("assets/sports/logos")
ensure_directory_permissions(logo_dir, get_assets_dir_mode())
Saving a File
Before (incorrect):
with open("config/my_config.json", 'w') as f:
json.dump(data, f, indent=4)
# Problem: File may not be readable by root service
After (correct):
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_config_dir_mode,
get_config_file_mode
)
config_path = Path("config/my_config.json")
# Ensure directory exists with proper permissions
ensure_directory_permissions(config_path.parent, get_config_dir_mode())
# Write file
with open(config_path, 'w') as f:
json.dump(data, f, indent=4)
# Set file permissions
ensure_file_permissions(config_path, get_config_file_mode(config_path))
Downloading and Saving an Image
Before (incorrect):
response = requests.get(image_url)
with open("assets/sports/logo.png", 'wb') as f:
f.write(response.content)
# Problem: File may not be writable by root service
After (correct):
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_assets_dir_mode,
get_assets_file_mode
)
logo_path = Path("assets/sports/logo.png")
# Ensure directory exists
ensure_directory_permissions(logo_path.parent, get_assets_dir_mode())
# Download and save
response = requests.get(image_url)
with open(logo_path, 'wb') as f:
f.write(response.content)
# Set file permissions
ensure_file_permissions(logo_path, get_assets_file_mode())
Common Patterns and Examples
Pattern 1: Config File Save
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_config_dir_mode,
get_config_file_mode
)
def save_config(config_data: dict, config_path: str) -> None:
"""Save configuration file with proper permissions."""
path = Path(config_path)
# Ensure directory exists
ensure_directory_permissions(path.parent, get_config_dir_mode())
# Write file
with open(path, 'w') as f:
json.dump(config_data, f, indent=4)
# Set permissions
ensure_file_permissions(path, get_config_file_mode(path))
Pattern 2: Asset Directory Setup
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
get_assets_dir_mode
)
def setup_asset_directory(base_dir: str, subdir: str) -> Path:
"""Create asset directory with proper permissions."""
asset_dir = Path(base_dir) / subdir
ensure_directory_permissions(asset_dir, get_assets_dir_mode())
return asset_dir
Pattern 3: Plugin File Creation
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_plugin_dir_mode,
get_plugin_file_mode
)
def save_plugin_data(plugin_id: str, data: dict) -> None:
"""Save plugin data file with proper permissions."""
plugin_dir = Path("plugins") / plugin_id
data_file = plugin_dir / "data.json"
# Ensure plugin directory exists
ensure_directory_permissions(plugin_dir, get_plugin_dir_mode())
# Write file
with open(data_file, 'w') as f:
json.dump(data, f, indent=2)
# Set permissions
ensure_file_permissions(data_file, get_plugin_file_mode())
Pattern 4: Cache Directory Creation
from pathlib import Path
from src.common.permission_utils import (
ensure_directory_permissions,
get_cache_dir_mode
)
def get_cache_directory() -> Path:
"""Get or create cache directory with proper permissions."""
cache_dir = Path("/var/cache/ledmatrix")
ensure_directory_permissions(cache_dir, get_cache_dir_mode())
return cache_dir
Pattern 5: Atomic File Write with Permissions
from pathlib import Path
import tempfile
import os
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_config_dir_mode,
get_config_file_mode
)
def save_config_atomic(config_data: dict, config_path: str) -> None:
"""Save config file atomically with proper permissions."""
path = Path(config_path)
# Ensure directory exists
ensure_directory_permissions(path.parent, get_config_dir_mode())
# Write to temp file first
temp_path = path.with_suffix('.tmp')
with open(temp_path, 'w') as f:
json.dump(config_data, f, indent=4)
# Set permissions on temp file
ensure_file_permissions(temp_path, get_config_file_mode(path))
# Atomic move
temp_path.replace(path)
# Permissions are preserved after move, but ensure they're correct
ensure_file_permissions(path, get_config_file_mode(path))
Permission Standards
File Permissions
| File Type | Mode | Octal | Description |
|---|---|---|---|
| Config files | rw-r--r-- |
0o644 |
Readable by all, writable by owner |
| Secrets files | rw-r----- |
0o640 |
Readable by owner and group only |
| Asset files | rw-rw-r-- |
0o664 |
Group-writable for root:user access |
| Plugin files | rw-rw-r-- |
0o664 |
Group-writable for root:user access |
Directory Permissions
| Directory Type | Mode | Octal | Description |
|---|---|---|---|
| Config directories | rwxrwsr-x |
0o2775 (setgid) |
Group-writable with setgid bit for inherited group ownership |
| Asset directories | rwxrwsr-x |
0o2775 (setgid) |
Group-writable with setgid bit for inherited group ownership |
| Plugin directories | rwxrwsr-x |
0o2775 (setgid) |
Group-writable with setgid bit for inherited group ownership |
| Cache directories | rwxrwsr-x |
0o2775 (setgid) |
Group-writable with setgid bit for inherited group ownership |
Why These Permissions?
- Group-writable (664): Allows both root service and web user to read/write files
- Directory setgid bit (2775): Ensures new files and directories inherit the group ownership, maintaining consistent permissions
- World-readable (644): Config files need to be readable by root service
- Restricted (640): Secrets files should only be readable by owner and group
Troubleshooting
Common Issues
Issue: Permission denied when saving config
Symptoms:
PermissionError: [Errno 13] Permission denied: 'config/config.json'
Solution:
Ensure you're using ensure_directory_permissions() and ensure_file_permissions():
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_config_dir_mode,
get_config_file_mode
)
path = Path("config/config.json")
ensure_directory_permissions(path.parent, get_config_dir_mode())
# ... write file ...
ensure_file_permissions(path, get_config_file_mode(path))
Issue: Logo downloads fail with permission errors
Symptoms:
PermissionError: Cannot write to directory assets/sports/logos
Solution: Use permission utilities when creating directories and saving files:
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_assets_dir_mode,
get_assets_file_mode
)
logo_path = Path("assets/sports/logos/team.png")
ensure_directory_permissions(logo_path.parent, get_assets_dir_mode())
# ... download and save ...
ensure_file_permissions(logo_path, get_assets_file_mode())
Issue: Files created by root service not accessible by web user
Symptoms:
- Web interface can't read files created by the service
- Files show as owned by root with restrictive permissions
Solution: Always use permission utilities when creating files. The utilities set group-writable permissions (664/775) that allow both users to access files.
Issue: Plugin can't write to its directory
Symptoms:
PermissionError: Cannot write to plugins/my-plugin/data.json
Solution: Use permission utilities in your plugin:
from src.common.permission_utils import (
ensure_directory_permissions,
ensure_file_permissions,
get_plugin_dir_mode,
get_plugin_file_mode
)
# In your plugin code
plugin_dir = Path("plugins") / self.plugin_id
ensure_directory_permissions(plugin_dir, get_plugin_dir_mode())
# ... create files ...
ensure_file_permissions(file_path, get_plugin_file_mode())
Verification
To verify permissions are set correctly:
# Check file permissions
ls -l config/config.json
# Should show: -rw-r--r-- or -rw-rw-r--
# Check directory permissions
ls -ld assets/sports/logos
# Should show: drwxrwxr-x or drwxr-xr-x
# Check if both users can access
sudo -u root test -r config/config.json && echo "Root can read"
sudo -u $USER test -r config/config.json && echo "User can read"
Manual Fix
If you need to manually fix permissions:
# Fix assets directory
sudo ./scripts/fix_perms/fix_assets_permissions.sh
# Fix plugin directory
sudo ./scripts/fix_perms/fix_plugin_permissions.sh
# Fix config directory
sudo chmod 755 config
sudo chmod 644 config/config.json
sudo chmod 640 config/config_secrets.json
Best Practices
- Always use permission utilities when creating files or directories
- Use the appropriate mode helper (
get_assets_file_mode(), etc.) rather than hardcoding modes - Set directory permissions before creating files in that directory
- Set file permissions immediately after writing the file
- Use atomic writes (temp file + move) for critical files like config
- Test with both users - verify files work when created by root service and web user
Integration with Core Utilities
Many core utilities already handle permissions automatically:
- LogoHelper (
src/common/logo_helper.py) - Sets permissions when downloading logos - LogoDownloader (
src/logo_downloader.py) - Sets permissions for directories and files - CacheManager - Sets permissions when creating cache directories
- ConfigManager - Sets permissions when saving config files
- PluginManager - Sets permissions for plugin directories and marker files
If you're using these utilities, you don't need to manually set permissions. However, if you're creating files directly (not through these utilities), you should use the permission utilities.
Summary
- Always use
ensure_directory_permissions()when creating directories - Always use
ensure_file_permissions()after writing files - Use mode helpers (
get_assets_file_mode(), etc.) for consistency - Core utilities handle permissions - you only need to set permissions for custom file operations
- Group-writable permissions (664/775) allow both root service and web user to access files
For questions or issues, refer to the troubleshooting section or check existing code in the LEDMatrix codebase for examples.