Files
LEDMatrix/docs/archive/PERMISSION_MANAGEMENT_GUIDE.md
Chuck ddd300a117 Docs/consolidate documentation (#217)
* 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>
2026-01-29 10:32:00 -05:00

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

  1. Why Permission Management Matters
  2. Permission Utilities
  3. When to Use Permission Utilities
  4. How to Use Permission Utilities
  5. Common Patterns and Examples
  6. Permission Standards
  7. Troubleshooting

Why Permission Management Matters

The Problem

Without proper permission management, you may encounter errors like:

  • PermissionError: [Errno 13] Permission denied when saving config files
  • PermissionError when 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 0o640 for secrets files, 0o644 for regular config files
  • get_assets_file_mode() -> int

    • Returns 0o664 (rw-rw-r--) for asset files (logos, images)
  • get_assets_dir_mode() -> int

    • Returns 0o2775 (rwxrwsr-x) for asset directories
    • Setgid bit enforces inherited group ownership for new files/directories
  • get_config_dir_mode() -> int

    • Returns 0o2775 (rwxrwsr-x) for config directories
    • Setgid bit enforces inherited group ownership for new files/directories
  • get_plugin_file_mode() -> int

    • Returns 0o664 (rw-rw-r--) for plugin files
  • get_plugin_dir_mode() -> int

    • Returns 0o2775 (rwxrwsr-x) for plugin directories
    • Setgid bit enforces inherited group ownership for new files/directories
  • get_cache_dir_mode() -> int

    • Returns 0o2775 (rwxrwsr-x) for cache directories
    • Setgid bit enforces inherited group ownership for new files/directories

When to Use Permission Utilities

Always Use Permission Utilities When:

  1. Creating directories - Use ensure_directory_permissions() instead of os.makedirs() or Path.mkdir()
  2. Saving files - Use ensure_file_permissions() after writing files
  3. Downloading assets - Set permissions after downloading logos, images, or other assets
  4. Creating config files - Set permissions after saving configuration files
  5. Creating cache files - Set permissions when creating cache directories or files
  6. Plugin file operations - Set permissions when plugins create their own files/directories

You Don't Need Permission Utilities When:

  1. Reading files - Reading doesn't require permission changes
  2. Using core utilities - Core utilities (LogoHelper, CacheManager, ConfigManager) already handle permissions
  3. Temporary files - Files in /tmp or created with tempfile don'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

  1. Always use permission utilities when creating files or directories
  2. Use the appropriate mode helper (get_assets_file_mode(), etc.) rather than hardcoding modes
  3. Set directory permissions before creating files in that directory
  4. Set file permissions immediately after writing the file
  5. Use atomic writes (temp file + move) for critical files like config
  6. 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.