persistent config file via config.template.json and migrate_config.sh

This commit is contained in:
Chuck
2025-09-15 10:27:16 -04:00
parent 4b36937a55
commit fcbc67464d
73 changed files with 429 additions and 1330 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@ __pycache__/
# Secrets # Secrets
config/config_secrets.json config/config_secrets.json
config/config.json
config/config.json.backup
credentials.json credentials.json
token.pickle token.pickle

View File

@@ -260,16 +260,38 @@ sudo reboot
## Configuration ## Configuration
1.Edit `config/config.json` with your preferences via `sudo nano config/config.json` ### Initial Setup
###API Keys The system uses a template-based configuration approach to avoid Git conflicts during updates:
1. **First-time setup**: Copy the template to create your config:
```bash
cp config/config.template.json config/config.json
```
2. **Edit your configuration**:
```bash
sudo nano config/config.json
```
or edit via web interface at http://ledpi:5001
### API Keys and Secrets
For sensitive settings like API keys: For sensitive settings like API keys:
Copy the template: `cp config/config_secrets.template.json config/config_secrets.json` 1. Copy the secrets template: `cp config/config_secrets.template.json config/config_secrets.json`
Edit `config/config_secrets.json` with your API keys via `sudo nano config/config_secrets.json` 2. Edit `config/config_secrets.json` with your API keys via `sudo nano config/config_secrets.json`
Ctrl + X to exit, Y to overwrite, Enter to Confirm 3. Ctrl + X to exit, Y to overwrite, Enter to Confirm
Everything is configured via `config/config.json` and `config/config_secrets.json`. ### Automatic Configuration Migration
The system automatically handles configuration updates:
- **New installations**: Creates `config.json` from the template automatically
- **Existing installations**: Automatically adds new configuration options with default values when the system starts
- **Backup protection**: Creates a backup of your current config before applying updates
- **No conflicts**: Your custom settings are preserved while new options are added
Everything is configured via `config/config.json` and `config/config_secrets.json`. The `config.json` file is not tracked by Git to prevent conflicts during updates.

View File

@@ -5,10 +5,10 @@
"start_time": "07:00", "start_time": "07:00",
"end_time": "23:00" "end_time": "23:00"
}, },
"timezone": "America/New_York", "timezone": "America/Chicago",
"location": { "location": {
"city": "Tampa", "city": "Dallas",
"state": "Florida", "state": "Texas",
"country": "US" "country": "US"
}, },
"display": { "display": {
@@ -39,7 +39,6 @@
"daily_forecast": 30, "daily_forecast": 30,
"stock_news": 20, "stock_news": 20,
"odds_ticker": 60, "odds_ticker": 60,
"leaderboard": 60,
"nhl_live": 30, "nhl_live": 30,
"nhl_recent": 30, "nhl_recent": 30,
"nhl_upcoming": 30, "nhl_upcoming": 30,
@@ -82,10 +81,10 @@
"update_interval": 1 "update_interval": 1
}, },
"weather": { "weather": {
"enabled": true, "enabled": false,
"update_interval": 1800, "update_interval": 1800,
"units": "imperial", "units": "imperial",
"display_format": "{temp}\u00b0F\n{condition}" "display_format": "{temp}°F\n{condition}"
}, },
"stocks": { "stocks": {
"enabled": false, "enabled": false,
@@ -130,12 +129,11 @@
"duration_buffer": 0.1 "duration_buffer": 0.1
}, },
"odds_ticker": { "odds_ticker": {
"enabled": false, "enabled": true,
"show_favorite_teams_only": true, "show_favorite_teams_only": true,
"games_per_favorite_team": 1, "games_per_favorite_team": 1,
"max_games_per_league": 5, "max_games_per_league": 5,
"show_odds_only": false, "show_odds_only": false,
"fetch_odds": true,
"sort_order": "soonest", "sort_order": "soonest",
"enabled_leagues": [ "enabled_leagues": [
"nfl", "nfl",
@@ -152,46 +150,6 @@
"dynamic_duration": true, "dynamic_duration": true,
"min_duration": 30, "min_duration": 30,
"max_duration": 300, "max_duration": 300,
"duration_buffer": 0.05
},
"leaderboard": {
"enabled": false,
"enabled_sports": {
"nfl": {
"enabled": true,
"top_teams": 10
},
"nba": {
"enabled": false,
"top_teams": 10
},
"mlb": {
"enabled": false,
"top_teams": 10
},
"ncaa_fb": {
"enabled": true,
"top_teams": 25,
"show_ranking": true
},
"nhl": {
"enabled": false,
"top_teams": 10
},
"ncaam_basketball": {
"enabled": false,
"top_teams": 25
}
},
"update_interval": 3600,
"scroll_speed": 1,
"scroll_delay": 0.01,
"display_duration": 60,
"loop": false,
"request_timeout": 30,
"dynamic_duration": true,
"min_duration": 45,
"max_duration": 600,
"duration_buffer": 0.1 "duration_buffer": 0.1
}, },
"calendar": { "calendar": {
@@ -264,6 +222,8 @@
"live_update_interval": 30, "live_update_interval": 30,
"live_odds_update_interval": 3600, "live_odds_update_interval": 3600,
"odds_update_interval": 3600, "odds_update_interval": 3600,
"recent_update_interval": 3600,
"upcoming_update_interval": 3600,
"recent_games_to_show": 1, "recent_games_to_show": 1,
"upcoming_games_to_show": 1, "upcoming_games_to_show": 1,
"show_favorite_teams_only": true, "show_favorite_teams_only": true,
@@ -299,10 +259,9 @@
], ],
"logo_dir": "assets/sports/ncaa_fbs_logos", "logo_dir": "assets/sports/ncaa_fbs_logos",
"show_records": true, "show_records": true,
"show_ranking": true,
"display_modes": { "display_modes": {
"ncaa_fb_live": true, "ncaa_fb_live": true,
"ncaa_fb_recent": true , "ncaa_fb_recent": true,
"ncaa_fb_upcoming": true "ncaa_fb_upcoming": true
} }
}, },
@@ -409,7 +368,7 @@
} }
}, },
"text_display": { "text_display": {
"enabled": true, "enabled": false,
"text": "Subscribe to ChuckBuilds", "text": "Subscribe to ChuckBuilds",
"font_path": "assets/fonts/press-start-2p.ttf", "font_path": "assets/fonts/press-start-2p.ttf",
"font_size": 8, "font_size": 8,

View File

@@ -150,17 +150,18 @@ echo ""
echo "This script will perform the following steps:" echo "This script will perform the following steps:"
echo "1. Install system dependencies" echo "1. Install system dependencies"
echo "2. Fix cache permissions" echo "2. Fix cache permissions"
echo "3. Install main LED Matrix service" echo "3. Fix assets directory permissions"
echo "4. Install Python project dependencies (requirements.txt)" echo "4. Install main LED Matrix service"
echo "5. Build and install rpi-rgb-led-matrix and test import" echo "5. Install Python project dependencies (requirements.txt)"
echo "6. Install web interface dependencies" echo "6. Build and install rpi-rgb-led-matrix and test import"
echo "7. Install web interface service" echo "7. Install web interface dependencies"
echo "8. Configure web interface permissions" echo "8. Install web interface service"
echo "9. Configure passwordless sudo access" echo "9. Configure web interface permissions"
echo "10. Set up proper file ownership" echo "10. Configure passwordless sudo access"
echo "11. Configure sound module to avoid conflicts" echo "11. Set up proper file ownership"
echo "12. Apply performance optimizations" echo "12. Configure sound module to avoid conflicts"
echo "13. Test the installation" echo "13. Apply performance optimizations"
echo "14. Test the installation"
echo "" echo ""
# Ask for confirmation # Ask for confirmation
@@ -217,8 +218,57 @@ else
fi fi
echo "" echo ""
CURRENT_STEP="Fix assets directory permissions"
echo "Step 3: Fixing assets directory permissions..."
echo "--------------------------------------------"
# Run the assets permissions fix
if [ -f "$PROJECT_ROOT_DIR/fix_assets_permissions.sh" ]; then
echo "Running assets permissions fix..."
bash "$PROJECT_ROOT_DIR/fix_assets_permissions.sh"
echo "✓ Assets permissions fixed"
else
echo "⚠ Assets permissions script not found, fixing permissions manually..."
# Set ownership of the entire assets directory to the real user
echo "Setting ownership of assets directory..."
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/assets"
# Set permissions to allow read/write for owner and group, read for others
echo "Setting permissions for assets directory..."
chmod -R 775 "$PROJECT_ROOT_DIR/assets"
# Specifically ensure the sports logos directories are writable
SPORTS_DIRS=(
"sports/ncaa_fbs_logos"
"sports/nfl_logos"
"sports/nba_logos"
"sports/nhl_logos"
"sports/mlb_logos"
"sports/milb_logos"
"sports/soccer_logos"
)
echo "Ensuring sports logo directories are writable..."
for SPORTS_DIR in "${SPORTS_DIRS[@]}"; do
FULL_PATH="$PROJECT_ROOT_DIR/assets/$SPORTS_DIR"
if [ -d "$FULL_PATH" ]; then
chmod 775 "$FULL_PATH"
chown "$ACTUAL_USER:$ACTUAL_USER" "$FULL_PATH"
else
echo "Creating directory: $FULL_PATH"
mkdir -p "$FULL_PATH"
chown "$ACTUAL_USER:$ACTUAL_USER" "$FULL_PATH"
chmod 775 "$FULL_PATH"
fi
done
echo "✓ Assets permissions fixed manually"
fi
echo ""
CURRENT_STEP="Install main LED Matrix service" CURRENT_STEP="Install main LED Matrix service"
echo "Step 3: Installing main LED Matrix service..." echo "Step 4: Installing main LED Matrix service..."
echo "---------------------------------------------" echo "---------------------------------------------"
# Run the main service installation (idempotent) # Run the main service installation (idempotent)
@@ -233,14 +283,52 @@ else
fi fi
echo "" echo ""
CURRENT_STEP="Ensure secrets configuration exists" CURRENT_STEP="Ensure configuration files exist"
echo "Step 3.1: Ensuring secrets configuration exists..." echo "Step 4.1: Ensuring configuration files exist..."
echo "-----------------------------------------------" echo "------------------------------------------------"
# Ensure config directory exists # Ensure config directory exists
mkdir -p "$PROJECT_ROOT_DIR/config" mkdir -p "$PROJECT_ROOT_DIR/config"
chmod 755 "$PROJECT_ROOT_DIR/config" || true chmod 755 "$PROJECT_ROOT_DIR/config" || true
# Create config.json from template if missing
if [ ! -f "$PROJECT_ROOT_DIR/config/config.json" ]; then
if [ -f "$PROJECT_ROOT_DIR/config/config.template.json" ]; then
echo "Creating config/config.json from template..."
cp "$PROJECT_ROOT_DIR/config/config.template.json" "$PROJECT_ROOT_DIR/config/config.json"
chown "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/config/config.json" || true
chmod 644 "$PROJECT_ROOT_DIR/config/config.json"
echo "✓ Main config file created from template"
else
echo "⚠ Template config/config.template.json not found; creating a minimal config file"
cat > "$PROJECT_ROOT_DIR/config/config.json" <<'EOF'
{
"web_display_autostart": true,
"timezone": "America/Chicago",
"display": {
"hardware": {
"rows": 32,
"cols": 64,
"chain_length": 2,
"parallel": 1,
"brightness": 95,
"hardware_mapping": "adafruit-hat-pwm"
}
},
"clock": {
"enabled": true,
"format": "%I:%M %p"
}
}
EOF
chown "$ACTUAL_USER:$ACTUAL_USER" "$PROJECT_ROOT_DIR/config/config.json" || true
chmod 644 "$PROJECT_ROOT_DIR/config/config.json"
echo "✓ Minimal config file created"
fi
else
echo "✓ Main config file already exists"
fi
# Create config_secrets.json from template if missing # Create config_secrets.json from template if missing
if [ ! -f "$PROJECT_ROOT_DIR/config/config_secrets.json" ]; then if [ ! -f "$PROJECT_ROOT_DIR/config/config_secrets.json" ]; then
if [ -f "$PROJECT_ROOT_DIR/config/config_secrets.template.json" ]; then if [ -f "$PROJECT_ROOT_DIR/config/config_secrets.template.json" ]; then
@@ -263,12 +351,12 @@ EOF
echo "✓ Minimal secrets file created" echo "✓ Minimal secrets file created"
fi fi
else else
echo "Secrets file already exists; leaving as-is" echo "Secrets file already exists"
fi fi
echo "" echo ""
CURRENT_STEP="Install project Python dependencies" CURRENT_STEP="Install project Python dependencies"
echo "Step 4: Installing Python project dependencies..." echo "Step 5: Installing Python project dependencies..."
echo "-----------------------------------------------" echo "-----------------------------------------------"
# Install main project Python dependencies # Install main project Python dependencies
@@ -283,7 +371,7 @@ echo "✓ Project Python dependencies installed"
echo "" echo ""
CURRENT_STEP="Build and install rpi-rgb-led-matrix" CURRENT_STEP="Build and install rpi-rgb-led-matrix"
echo "Step 5: Building and installing rpi-rgb-led-matrix..." echo "Step 6: Building and installing rpi-rgb-led-matrix..."
echo "-----------------------------------------------------" echo "-----------------------------------------------------"
# If already installed and not forcing rebuild, skip expensive build # If already installed and not forcing rebuild, skip expensive build
@@ -327,7 +415,7 @@ fi
echo "" echo ""
CURRENT_STEP="Install web interface dependencies" CURRENT_STEP="Install web interface dependencies"
echo "Step 6: Installing web interface dependencies..." echo "Step 7: Installing web interface dependencies..."
echo "------------------------------------------------" echo "------------------------------------------------"
# Install web interface dependencies # Install web interface dependencies
@@ -335,9 +423,9 @@ echo "Installing Python dependencies for web interface..."
cd "$PROJECT_ROOT_DIR" cd "$PROJECT_ROOT_DIR"
# Try to install dependencies using the smart installer if available # Try to install dependencies using the smart installer if available
if [ -f "$PROJECT_ROOT_DIR/install_dependencies_apt.py" ]; then if [ -f "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py" ]; then
echo "Using smart dependency installer..." echo "Using smart dependency installer..."
python3 "$PROJECT_ROOT_DIR/install_dependencies_apt.py" python3 "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py"
else else
echo "Using pip to install dependencies..." echo "Using pip to install dependencies..."
if [ -f "$PROJECT_ROOT_DIR/requirements_web_v2.txt" ]; then if [ -f "$PROJECT_ROOT_DIR/requirements_web_v2.txt" ]; then
@@ -351,7 +439,7 @@ echo "✓ Web interface dependencies installed"
echo "" echo ""
CURRENT_STEP="Install web interface service" CURRENT_STEP="Install web interface service"
echo "Step 7: Installing web interface service..." echo "Step 8: Installing web interface service..."
echo "-------------------------------------------" echo "-------------------------------------------"
if [ -f "$PROJECT_ROOT_DIR/install_web_service.sh" ]; then if [ -f "$PROJECT_ROOT_DIR/install_web_service.sh" ]; then
@@ -369,7 +457,7 @@ fi
echo "" echo ""
CURRENT_STEP="Harden systemd unit file permissions" CURRENT_STEP="Harden systemd unit file permissions"
echo "Step 7.1: Setting systemd unit file permissions..." echo "Step 8.1: Setting systemd unit file permissions..."
echo "-----------------------------------------------" echo "-----------------------------------------------"
for unit in "/etc/systemd/system/ledmatrix.service" "/etc/systemd/system/ledmatrix-web.service"; do for unit in "/etc/systemd/system/ledmatrix.service" "/etc/systemd/system/ledmatrix-web.service"; do
if [ -f "$unit" ]; then if [ -f "$unit" ]; then
@@ -382,7 +470,7 @@ echo "✓ Systemd unit file permissions set"
echo "" echo ""
CURRENT_STEP="Configure web interface permissions" CURRENT_STEP="Configure web interface permissions"
echo "Step 8: Configuring web interface permissions..." echo "Step 9: Configuring web interface permissions..."
echo "------------------------------------------------" echo "------------------------------------------------"
# Add user to required groups (idempotent) # Add user to required groups (idempotent)
@@ -404,7 +492,7 @@ echo "✓ User added to required groups"
echo "" echo ""
CURRENT_STEP="Configure passwordless sudo access" CURRENT_STEP="Configure passwordless sudo access"
echo "Step 9: Configuring passwordless sudo access..." echo "Step 10: Configuring passwordless sudo access..."
echo "------------------------------------------------" echo "------------------------------------------------"
# Create sudoers configuration for the web interface # Create sudoers configuration for the web interface
@@ -451,7 +539,7 @@ echo "✓ Passwordless sudo access configured"
echo "" echo ""
CURRENT_STEP="Set proper file ownership" CURRENT_STEP="Set proper file ownership"
echo "Step 10: Setting proper file ownership..." echo "Step 11: Setting proper file ownership..."
echo "----------------------------------------" echo "----------------------------------------"
# Set ownership of project files to the user # Set ownership of project files to the user
@@ -475,7 +563,7 @@ echo "✓ File ownership configured"
echo "" echo ""
CURRENT_STEP="Normalize project file permissions" CURRENT_STEP="Normalize project file permissions"
echo "Step 10.1: Normalizing project file and directory permissions..." echo "Step 11.1: Normalizing project file and directory permissions..."
echo "--------------------------------------------------------------" echo "--------------------------------------------------------------"
# Normalize directory permissions (exclude VCS metadata) # Normalize directory permissions (exclude VCS metadata)
@@ -489,14 +577,14 @@ find "$PROJECT_ROOT_DIR" -path "*/.git*" -prune -o -type f -name "*.sh" -exec ch
# Explicitly ensure common helper scripts are executable (in case paths change) # Explicitly ensure common helper scripts are executable (in case paths change)
chmod 755 "$PROJECT_ROOT_DIR/start_display.sh" "$PROJECT_ROOT_DIR/stop_display.sh" 2>/dev/null || true chmod 755 "$PROJECT_ROOT_DIR/start_display.sh" "$PROJECT_ROOT_DIR/stop_display.sh" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT_DIR/fix_cache_permissions.sh" "$PROJECT_ROOT_DIR/fix_web_permissions.sh" 2>/dev/null || true chmod 755 "$PROJECT_ROOT_DIR/fix_cache_permissions.sh" "$PROJECT_ROOT_DIR/fix_web_permissions.sh" "$PROJECT_ROOT_DIR/fix_assets_permissions.sh" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT_DIR/install_service.sh" "$PROJECT_ROOT_DIR/install_web_service.sh" 2>/dev/null || true chmod 755 "$PROJECT_ROOT_DIR/install_service.sh" "$PROJECT_ROOT_DIR/install_web_service.sh" 2>/dev/null || true
echo "✓ Project file permissions normalized" echo "✓ Project file permissions normalized"
echo "" echo ""
CURRENT_STEP="Sound module configuration" CURRENT_STEP="Sound module configuration"
echo "Step 11: Sound module configuration..." echo "Step 12: Sound module configuration..."
echo "-------------------------------------" echo "-------------------------------------"
# Remove services that may interfere with LED matrix timing # Remove services that may interfere with LED matrix timing
@@ -539,7 +627,7 @@ echo "✓ Sound module configuration applied"
echo "" echo ""
CURRENT_STEP="Apply performance optimizations" CURRENT_STEP="Apply performance optimizations"
echo "Step 12: Applying performance optimizations..." echo "Step 13: Applying performance optimizations..."
echo "---------------------------------------------" echo "---------------------------------------------"
# Prefer /boot/firmware on newer Raspberry Pi OS, fall back to /boot on older # Prefer /boot/firmware on newer Raspberry Pi OS, fall back to /boot on older
@@ -588,7 +676,7 @@ echo "✓ Performance optimizations applied"
echo "" echo ""
CURRENT_STEP="Test the installation" CURRENT_STEP="Test the installation"
echo "Step 13: Testing the installation..." echo "Step 14: Testing the installation..."
echo "----------------------------------" echo "----------------------------------"
# Test sudo access # Test sudo access
@@ -665,7 +753,8 @@ echo "Enable/disable web interface autostart:"
echo " Edit config/config.json and set 'web_display_autostart': true" echo " Edit config/config.json and set 'web_display_autostart': true"
echo "" echo ""
echo "Configuration files:" echo "Configuration files:"
echo " Main config: config/config.json" echo " Main config: config/config.json (created from template automatically)"
echo " Secrets: config/config_secrets.json (create from template if needed)" echo " Secrets: config/config_secrets.json (created from template automatically)"
echo " Template: config/config.template.json (reference for new options)"
echo "" echo ""
echo "Enjoy your LED Matrix display!" echo "Enjoy your LED Matrix display!"

127
fix_assets_permissions.sh Normal file
View File

@@ -0,0 +1,127 @@
#!/bin/bash
# LEDMatrix Assets Permissions Fix Script
# This script fixes permissions on the assets directory so the application can download and save team logos
echo "Fixing LEDMatrix assets directory permissions..."
# Get the real user (not root when running with sudo)
REAL_USER=${SUDO_USER:-$USER}
# Resolve the home directory of the real user robustly
if command -v getent >/dev/null 2>&1; then
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
else
REAL_HOME=$(eval echo ~"$REAL_USER")
fi
REAL_GROUP=$(id -gn "$REAL_USER")
# Get the project directory
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ASSETS_DIR="$PROJECT_DIR/assets"
echo "Project directory: $PROJECT_DIR"
echo "Assets directory: $ASSETS_DIR"
echo "Real user: $REAL_USER"
echo "Real group: $REAL_GROUP"
# Check if assets directory exists
if [ ! -d "$ASSETS_DIR" ]; then
echo "Error: Assets directory does not exist at $ASSETS_DIR"
exit 1
fi
echo ""
echo "Fixing permissions for assets directory and subdirectories..."
# Set ownership of the entire assets directory to the real user
echo "Setting ownership of assets directory..."
if sudo chown -R "$REAL_USER:$REAL_GROUP" "$ASSETS_DIR"; then
echo "✓ Set assets directory ownership to $REAL_USER:$REAL_GROUP"
else
echo "✗ Failed to set assets directory ownership"
exit 1
fi
# Set permissions to allow read/write for owner and group, read for others
echo "Setting permissions for assets directory..."
if sudo chmod -R 775 "$ASSETS_DIR"; then
echo "✓ Set assets directory permissions to 775"
else
echo "✗ Failed to set assets directory permissions"
exit 1
fi
# Specifically ensure the sports logos directories are writable
SPORTS_DIRS=(
"sports/ncaa_fbs_logos"
"sports/nfl_logos"
"sports/nba_logos"
"sports/nhl_logos"
"sports/mlb_logos"
"sports/milb_logos"
"sports/soccer_logos"
)
echo ""
echo "Ensuring sports logo directories are writable..."
for SPORTS_DIR in "${SPORTS_DIRS[@]}"; do
FULL_PATH="$ASSETS_DIR/$SPORTS_DIR"
echo ""
echo "Checking directory: $FULL_PATH"
if [ -d "$FULL_PATH" ]; then
echo " - Directory exists"
echo " - Current permissions:"
ls -ld "$FULL_PATH"
# Ensure the directory is writable
sudo chmod 775 "$FULL_PATH"
sudo chown "$REAL_USER:$REAL_GROUP" "$FULL_PATH"
echo " - Updated permissions:"
ls -ld "$FULL_PATH"
# Test write access
echo " - Testing write access as $REAL_USER..."
if sudo -u "$REAL_USER" test -w "$FULL_PATH"; then
echo "$FULL_PATH is writable by $REAL_USER"
else
echo "$FULL_PATH is not writable by $REAL_USER"
fi
else
echo " - Directory does not exist, creating it..."
sudo mkdir -p "$FULL_PATH"
sudo chown "$REAL_USER:$REAL_GROUP" "$FULL_PATH"
sudo chmod 775 "$FULL_PATH"
echo " - Created directory with proper permissions"
fi
done
echo ""
echo "Testing write access to ncaa_fbs_logos directory specifically..."
NCAA_DIR="$ASSETS_DIR/sports/ncaa_fbs_logos"
if [ -d "$NCAA_DIR" ]; then
# Create a test file to verify write access
TEST_FILE="$NCAA_DIR/.permission_test"
if sudo -u "$REAL_USER" touch "$TEST_FILE" 2>/dev/null; then
echo "✓ Successfully created test file in ncaa_fbs_logos directory"
sudo -u "$REAL_USER" rm -f "$TEST_FILE"
echo "✓ Successfully removed test file"
else
echo "✗ Failed to create test file in ncaa_fbs_logos directory"
echo " This indicates the permission fix did not work properly"
fi
else
echo "✗ ncaa_fbs_logos directory does not exist"
fi
echo ""
echo "Assets permissions fix completed!"
echo ""
echo "The application should now be able to download and save team logos."
echo "If you still see permission errors, check which user is running the LEDMatrix service"
echo "and ensure it matches the owner above ($REAL_USER)."
echo ""
echo "You may need to restart the LEDMatrix service for the changes to take effect:"
echo " sudo systemctl restart ledmatrix.service"

43
migrate_config.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# LED Matrix Configuration Migration Script
# This script helps migrate existing config.json to the new template-based system
set -e
echo "=========================================="
echo "LED Matrix Configuration Migration Script"
echo "=========================================="
echo ""
# Check if we're in the right directory
if [ ! -f "config/config.template.json" ]; then
echo "Error: config/config.template.json not found."
echo "Please run this script from the LEDMatrix project root directory."
exit 1
fi
# Check if config.json exists
if [ ! -f "config/config.json" ]; then
echo "No existing config.json found. Creating from template..."
cp config/config.template.json config/config.json
echo "✓ Created config/config.json from template"
echo ""
echo "You can now edit config/config.json with your preferences."
exit 0
fi
echo "Existing config.json found. The system will automatically handle migration."
echo ""
echo "What this means:"
echo "- Your current config.json will be preserved"
echo "- New configuration options will be automatically added with default values"
echo "- A backup will be created before any changes"
echo "- The system handles this automatically when it starts"
echo ""
echo "No manual migration is needed. The ConfigManager will handle everything automatically."
echo ""
echo "To see the latest configuration options, you can reference:"
echo " config/config.template.json"
echo ""
echo "Migration complete!"

File diff suppressed because it is too large Load Diff

View File

@@ -101,7 +101,7 @@ def show_status():
def main(): def main():
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Usage: python3 enable_news_manager.py [enable|disable|status]") print("Usage: python3 scripts/enable_news_manager.py [enable|disable|status]")
sys.exit(1) sys.exit(1)
command = sys.argv[1].lower() command = sys.argv[1].lower()

View File

@@ -7,6 +7,7 @@ class ConfigManager:
# Use current working directory as base # Use current working directory as base
self.config_path = config_path or "config/config.json" self.config_path = config_path or "config/config.json"
self.secrets_path = secrets_path or "config/config_secrets.json" self.secrets_path = secrets_path or "config/config_secrets.json"
self.template_path = "config/config.template.json"
self.config: Dict[str, Any] = {} self.config: Dict[str, Any] = {}
def get_config_path(self) -> str: def get_config_path(self) -> str:
@@ -18,11 +19,18 @@ class ConfigManager:
def load_config(self) -> Dict[str, Any]: def load_config(self) -> Dict[str, Any]:
"""Load configuration from JSON files.""" """Load configuration from JSON files."""
try: try:
# Check if config file exists, if not create from template
if not os.path.exists(self.config_path):
self._create_config_from_template()
# Load main config # Load main config
print(f"Attempting to load config from: {os.path.abspath(self.config_path)}") print(f"Attempting to load config from: {os.path.abspath(self.config_path)}")
with open(self.config_path, 'r') as f: with open(self.config_path, 'r') as f:
self.config = json.load(f) self.config = json.load(f)
# Migrate config to add any new items from template
self._migrate_config()
# Load and merge secrets if they exist (be permissive on errors) # Load and merge secrets if they exist (be permissive on errors)
if os.path.exists(self.secrets_path): if os.path.exists(self.secrets_path):
try: try:
@@ -118,6 +126,85 @@ class ConfigManager:
else: else:
target[key] = value target[key] = value
def _create_config_from_template(self) -> None:
"""Create config.json from template if it doesn't exist."""
if not os.path.exists(self.template_path):
raise FileNotFoundError(f"Template file not found at {os.path.abspath(self.template_path)}")
print(f"Creating config.json from template at {os.path.abspath(self.template_path)}")
# Ensure config directory exists
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
# Copy template to config
with open(self.template_path, 'r') as template_file:
template_data = json.load(template_file)
with open(self.config_path, 'w') as config_file:
json.dump(template_data, config_file, indent=4)
print(f"Created config.json from template at {os.path.abspath(self.config_path)}")
def _migrate_config(self) -> None:
"""Migrate config to add new items from template with defaults."""
if not os.path.exists(self.template_path):
print(f"Template file not found at {os.path.abspath(self.template_path)}, skipping migration")
return
try:
with open(self.template_path, 'r') as f:
template_config = json.load(f)
# Check if migration is needed
if self._config_needs_migration(self.config, template_config):
print("Config migration needed - adding new configuration items with defaults")
# Create backup of current config
backup_path = f"{self.config_path}.backup"
with open(backup_path, 'w') as backup_file:
json.dump(self.config, backup_file, indent=4)
print(f"Created backup of current config at {os.path.abspath(backup_path)}")
# Merge template defaults into current config
self._merge_template_defaults(self.config, template_config)
# Save migrated config
with open(self.config_path, 'w') as f:
json.dump(self.config, f, indent=4)
print(f"Config migration completed and saved to {os.path.abspath(self.config_path)}")
else:
print("Config is up to date, no migration needed")
except Exception as e:
print(f"Error during config migration: {e}")
# Don't raise - continue with current config
def _config_needs_migration(self, current_config: Dict[str, Any], template_config: Dict[str, Any]) -> bool:
"""Check if config needs migration by comparing with template."""
return self._has_new_keys(current_config, template_config)
def _has_new_keys(self, current: Dict[str, Any], template: Dict[str, Any]) -> bool:
"""Recursively check if template has keys not in current config."""
for key, value in template.items():
if key not in current:
return True
if isinstance(value, dict) and isinstance(current[key], dict):
if self._has_new_keys(current[key], value):
return True
return False
def _merge_template_defaults(self, current: Dict[str, Any], template: Dict[str, Any]) -> None:
"""Recursively merge template defaults into current config."""
for key, value in template.items():
if key not in current:
# Add new key with template value
current[key] = value
print(f"Added new config key: {key}")
elif isinstance(value, dict) and isinstance(current[key], dict):
# Recursively merge nested dictionaries
self._merge_template_defaults(current[key], value)
def get_timezone(self) -> str: def get_timezone(self) -> str:
"""Get the configured timezone.""" """Get the configured timezone."""
return self.config.get('timezone', 'UTC') return self.config.get('timezone', 'UTC')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -206,13 +206,13 @@ This single script handles everything you need for a new installation.
### **Dependency Installation Scripts** ### **Dependency Installation Scripts**
#### `install_dependencies_apt.py` #### `scripts/install_dependencies_apt.py`
- **When to use**: When you want to install packages via apt first, then pip - **When to use**: When you want to install packages via apt first, then pip
- **What it does**: - **What it does**:
- Tries to install packages via apt (system packages) - Tries to install packages via apt (system packages)
- Falls back to pip with `--break-system-packages` - Falls back to pip with `--break-system-packages`
- Handles externally managed Python environments - Handles externally managed Python environments
- **Usage**: `sudo python3 install_dependencies_apt.py` - **Usage**: `sudo python3 scripts/install_dependencies_apt.py`
#### `start_web_v2.py` #### `start_web_v2.py`
- **When to use**: Manual web interface startup - **When to use**: Manual web interface startup
@@ -252,7 +252,7 @@ sudo ./first_time_install.sh
### **Scenario 2: Adding Web Interface to Existing Installation** ### **Scenario 2: Adding Web Interface to Existing Installation**
```bash ```bash
# Install web interface dependencies # Install web interface dependencies
sudo python3 install_dependencies_apt.py sudo python3 scripts/install_dependencies_apt.py
# Fix permissions # Fix permissions
./fix_web_permissions.sh ./fix_web_permissions.sh
@@ -332,7 +332,7 @@ http://your-pi-ip:5001
2. Log out and back in 2. Log out and back in
### **Dependency Installation Errors** ### **Dependency Installation Errors**
1. Run: `sudo python3 install_dependencies_apt.py` 1. Run: `sudo python3 scripts/install_dependencies_apt.py`
## Summary ## Summary

View File

@@ -50,17 +50,17 @@ Other: https://www.coveringthecorner.com/rss/current.xml
### Command Line Management ### Command Line Management
Use the `enable_news_manager.py` script to manage the news manager: Use the `scripts/enable_news_manager.py` script to manage the news manager:
```bash ```bash
# Check current status # Check current status
python3 enable_news_manager.py status python3 scripts/enable_news_manager.py status
# Enable news manager # Enable news manager
python3 enable_news_manager.py enable python3 scripts/enable_news_manager.py enable
# Disable news manager # Disable news manager
python3 enable_news_manager.py disable python3 scripts/enable_news_manager.py disable
``` ```
### Web Interface ### Web Interface